Learning path · Level 5 · Tutorial 6

Save and Restore Browser Preferences

Persist a non-sensitive display preference where browser storage is available, restore only supported values, and keep the interface usable when storage is missing, invalid, or blocked.

  • Level: Intermediate
  • Duration: 20 min
  • 8 steps
Start tutorial Back to the path
Save and Restore Browser Preferences

What you will go through

  1. Choose what belongs in browser storage Identify an appropriate page preference and understand the limited, optional role of browser storage. 2 min
  2. Read, write, and remove one string Use the three targeted localStorage operations for one string preference, while keeping every storage access inside a recovery boundary. 2 min
  3. Restore only supported values Validate a saved density string before it becomes Reading Room in-memory state, with a safe fallback for missing, unsupported, and unreadable storage. 3 min
  4. Restore before the first render Restore the Reading Room density before its first application render so the control and preview begin from the same supported in-memory state. 2 min
  5. Save changes without breaking the interface Update the Reading Room density preference in memory and report storage-write results accurately without breaking the current interface. 3 min
  6. Reset the preference safely Restore the Reading Room default immediately and remove only its saved density preference when possible. 2 min
  7. Interpret browser limits and test failures Interpret persistence outcomes by browsing context and plan reversible failure checks for the guarded Reading Room helpers. 2 min
  8. Build and verify a resilient preference Build the complete Reading Room example locally, then verify valid restoration, safe fallbacks, and truthful behavior when browser storage cannot be used. 5 min

What you will learn

  • Save and restore an allowed string preference through localStorage.
  • Apply a safe default when the saved value is missing or unsupported.
  • Keep preference changes working in memory when reading or writing storage throws an exception.
  • Check reload persistence in the learner's browser where supported and explain why local file behavior is not guaranteed.

Before you start

  • Keep Interface State and Rendering Consistent
  • Handle Failures with JavaScript Exceptions

Step 1 of 8

Choose what belongs in browser storage

Identify an appropriate page preference and understand the limited, optional role of browser storage.

A choice can outlive the current page

Two places for one choice

A page preference, not a browser setting

Reading Room can remember whether its reading preview feels Comfortable or Compact. That choice belongs to this web page’s interface. It does not change a setting for the browser application itself.

The current choice can live in in-memory state while this page is open. A saved preference is an extra record that the page may use again after a reload.

Current page and saved preference

A diagram contrasts state inside the current Reading Room page with a saved preference that can be used after the page reloads.

In-memory state serves the current page execution; a saved preference may be available to a later execution.

Use persistence carefully

Storage has a boundary

Browser storage is synchronous and stores strings. Its saved data is associated with a specific origin—the page’s scheme, host, and port—and with a browser profile.

So a preference saved for one site context is not a universal browser setting. It should not be treated as a backup or as guaranteed synchronization to another device.

Warning

Choose safe preference data

Use browser storage for a small, non-sensitive interface choice that improves a return visit. Do not put passwords, access tokens, or confidential information in this preference storage.

Stored data can be cleared, unavailable, or isolated by a different origin or browser profile. The Reading Room must remain useful even when no saved preference is available.

Good fit or poor fit?

Examples of what a page may remember and what it must not rely on browser preference storage to protect.

A display preference is small and non-sensitive; confidential data is not.
DataStorage fitWhy
Reading densityAppropriateNon-sensitive page preference
PasswordNot appropriateConfidential credential
Access tokenNot appropriateSensitive access data
Cross-device backupNot guaranteedStorage can be cleared or unavailable

Distinguish state from persistence

Check your understanding

Match each concept to its best description.

Where does each idea belong?

Match the concepts with their descriptions.

Tap an item and then its matching pair.

Step 2 of 8

Read, write, and remove one string

Use the three targeted localStorage operations for one string preference, while keeping every storage access inside a recovery boundary.

Use a precise key and operation

One named slot for one preference

The Reading Room needs to remember one small value: the display-density string. Use the namespaced key STORAGE_KEY = "reading-room:density" so this preference is distinct from other data on the same site.

A write under that key replaces its previous value. A read returns the saved string, or null if that key is absent. A removal deletes only that one key. Because the preference is already one string, store it directly—no conversion is needed.

Never use a broad operation that removes unrelated site data for this task.

How one storage key changes

Three panels show a single browser storage slot being replaced, read, and emptied.

A single named slot can be written, read, or removed without affecting other slots.

The three operations

Each call uses the same key.

Storage calls for the Reading Room preference
OperationCallResult
Write or replacewindow.localStorage.setItem(STORAGE_KEY, value)Stores the string
Readwindow.localStorage.getItem(STORAGE_KEY)Saved string or null
Removewindow.localStorage.removeItem(STORAGE_KEY)Deletes that key only

Make failures recoverable

Guard the whole storage access

Storage is optional from the page's perspective: a browser can throw while obtaining window.localStorage or while calling one of its methods. Put the complete property access and method call inside try.

The next step will decide which read values are allowed. For now, notice that a guarded read has a normal path and a recovery path; do not place window.localStorage in a variable before the try block.

Read attempt inside try

A guarded read keeps both the property access and the method call in the protected block.

javascript
try {
  const storedValue = window.localStorage.getItem(STORAGE_KEY);
  // Later, accept only supported values.
} catch (error) {
  // Choose a safe result for the current page.
}

Guarded writer and remover

The writer and remover report whether their storage operation completed. They do not update the interface themselves.

javascript
function saveDensity(value) {
  try {
    window.localStorage.setItem(STORAGE_KEY, value);
    return true;
  } catch (error) {
    return false;
  }
}

function removeSavedDensity() {
  try {
    window.localStorage.removeItem(STORAGE_KEY);
    return true;
  } catch (error) {
    return false;
  }
}

Tip

Keep other stored data intact

window.localStorage.removeItem(STORAGE_KEY) is narrow: it removes only the Reading Room preference. If that key is already absent and storage access succeeds, removal is still harmless. Do not remove all stored data when managing one preference.

Practice one complete storage lifecycle

Complete the guarded storage sequence

Complete the guarded sequence so it saves, retrieves, and removes the Reading Room preference, then checks the result of reading after removal.

javascript
try {
  window.localStorage.(STORAGE_KEY, "compact");
  const beforeRemoval = window.localStorage.(STORAGE_KEY);
  window.localStorage.(STORAGE_KEY);
  const afterRemoval = window.localStorage.(STORAGE_KEY);
  const isMissing = afterRemoval === ;
} catch (error) {
  // Storage access failed.
}

Step 3 of 8

Restore only supported values

Validate a saved density string before it becomes Reading Room in-memory state, with a safe fallback for missing, unsupported, and unreadable storage.

Separate missing from unsupported values

Validate before using a saved value

A stored value is not automatically safe for the Reading Room interface. null means the key is missing. A present string can still be unsupported—for example, an old value such as "spacious" or an empty string.

Treat storage as untrusted input at this boundary: accept only the two density strings the interface supports. Every other outcome uses the DEFAULT_DENSITY value, "comfortable".

A shortcut such as value || DEFAULT_DENSITY is insufficient. It falls back for null and "", but "spacious" is a truthy string and would incorrectly pass through.

A validation gate for saved data

A diagram shows browser storage values passing through a validation gate. Accepted density values reach the Reading Room interface, while missing or unsupported values go to the comfortable layout.

Validate the saved string before it becomes in-memory state.

Saved-value outcomes

The reader has a small, explicit allowlist.

Only exact supported strings may be returned from storage unchanged.
Storage resultMeaningReader result
nullKey is missingUse the default
"comfortable"Supported stringKeep it
"compact"Supported stringKeep it
""Unsupported empty stringUse the default
"spacious"Unsupported obsolete stringUse the default

Build a reader with a safe fallback

Implement `loadDensity()`

The guarded reader returns a value for the interface to use. It does not update storage.

javascript
const STORAGE_KEY = "reading-room:density";
const DEFAULT_DENSITY = "comfortable";

function loadDensity() {
  try {
    const value = window.localStorage.getItem(STORAGE_KEY);

    if (value === "comfortable" || value === "compact") {
      return value;
    }

    return DEFAULT_DENSITY;
  } catch (error) {
    return DEFAULT_DENSITY;
  }
}

Tip

One safe return contract

Both window.localStorage access and getItem() are inside try. If either throws, the catch returns the same safe default.

The if returns only exact allowed strings. null, "", and "spacious" reach the fallback return. This startup reader does not rewrite or remove an unsupported saved preference; it simply declines to use it.

Practice the validation branches

Complete the guarded reader

Complete the validation and normal fallback branch.

javascript
function loadDensity() {
  try {
    const value = window.localStorage.getItem(STORAGE_KEY);

    if (value ===  || value === ) {
      return value;
    }

    return ;
  } catch (error) {
    return DEFAULT_DENSITY;
  }
}

Apply the complete fallback rule

Trace every reader outcome

Without running code, write the exact if condition that accepts the two supported values. Then trace what loadDensity() returns when getItem() produces "comfortable", "compact", null, and "spacious", and when the read throws.

For each fallback case, state why it reaches that result.

Write at least 180 characters (0/180).

Step 4 of 8

Restore before the first render

Restore the Reading Room density before its first application render so the control and preview begin from the same supported in-memory state.

Start from one validated value

Start with a restored state

At startup, Reading Room has one direction: read and validate the saved preference → initialize in-memory state → render the interface.

loadDensity() already handles unavailable, missing, and unsupported storage values by returning a supported density. Put that result into state.density before the first render() call. Then the density selector and the reading preview begin in agreement.

Consult storage at this startup boundary—not inside render(). Rendering should apply the current in-memory state, not repeatedly read storage.

A flow diagram from saved browser data through validation and in-memory state to a matching density selector and reading preview.

Restore and validate the density before the interface is rendered for the first time.

Initialize, then render once

Keep storage out of render()

After the document is parsed, select the interface elements and define render() as usual. Then use this startup block. No storage write belongs here: an existing saved preference must get the chance to become state first.

preferences.js — startup sequence

Startup code placed after element selection and the render() function definition.

javascript
const restoredDensity = loadDensity();

const state = {
  density: restoredDensity,
  feedback: ""
};

render();

Tip

A useful boundary

This ordering improves the application’s initial state, but it does not promise control over every browser paint. The browser can display parsed document content before deferred JavaScript runs. The important application rule is that Reading Room’s first render() uses the restored, validated density—not an unconditionally chosen default.

Practice the sequence

Order the startup sequence

Put the Reading Room startup operations in their required order.

  1. Call render() once to apply state to the interface
  2. Create state with the returned density and empty feedback
  3. Call loadDensity() to perform the guarded read and validation

Explain the ordering

Check the initial behavior

Which option gives both the correct startup order and the reason not to render or save a default first?

Step 5 of 8

Save changes without breaking the interface

Update the Reading Room density preference in memory and report storage-write results accurately without breaking the current interface.

Keep the current page usable

Keep the current page usable

State first, persistence second

When a reader chooses a density, Reading Room first commits that choice to in-memory state. It then attempts to save the preference, records truthful feedback from the result, and calls render() either way.

A storage failure must not undo the choice or disable the selector. For example, Compact can still apply on the current page even when it cannot be saved. After a later reload, the page may restore an older saved preference—or the default—because the newest write did not persist.

One interaction, two persistence outcomes

A diagram showing a density selection updating in-memory state before a storage attempt; successful and failed writes both lead to an updated reading preview.

The interface renders the new state whether the write succeeds or throws.

Return a result instead of stopping the interaction

Guard the write

The following is a study fragment, not code to run yet. Step 8 will provide the complete local files and assemble this helper into preferences.js.

Keep both window.localStorage access and setItem() inside try. If either operation throws, saveDensity(value) catches the exception and returns false instead of interrupting the change flow. The density is already a string, so it is written directly.

saveDensity() returns a result

Study fragment for the complete example assembled in Step 8.

javascript
function saveDensity(value) {
  try {
    window.localStorage.setItem(STORAGE_KEY, value);
    return true;
  } catch (error) {
    return false;
  }
}

Tip

Interpret the result

true means the saved preference was written. false means the selected density still applies on the current page, but the page cannot claim that it was saved. The change handler—not the helper—uses that result to choose feedback.

Use the boolean result for feedback

Update, save, report, render

Study fragment for the complete example assembled in Step 8. The new state is assigned before the storage attempt, and render() follows either feedback outcome.

javascript
densitySelect.addEventListener("change", () => {
  state.density = densitySelect.value;

  if (saveDensity(state.density)) {
    state.feedback = "Preference saved.";
  } else {
    state.feedback = "Density applied for this page only.";
  }

  render();
});

Keep rendering free of storage side effects

render() reflects the existing in-memory state in the control, preview, and status region. It does not read or write storage.

javascript
function render() {
  densitySelect.value = state.density;
  readingPreview.classList.toggle(
    "is-compact",
    state.density === "compact"
  );
  preferenceStatus.textContent = state.feedback;
}

Tip

Allow later attempts

Because render() has no storage side effects, a later density change can attempt another save. One failed write does not lock the interface into an error state or roll back the current page’s choice.

Practice: preserve the new choice

Complete the resilient change flow

Complete this study example of the guarded save and change flow.

javascript
function saveDensity(value) {
  try {
    window.localStorage.(STORAGE_KEY, value);
    return ;
  } catch (error) {
    return ;
  }
}

densitySelect.addEventListener("change", () => {
  state.density = ;

  if (saveDensity(state.density)) {
    state.feedback = "Preference saved.";
  } else {
    state.feedback = "Density applied for this page only.";
  }

  ;
});

Step 6 of 8

Reset the preference safely

Restore the Reading Room default immediately and remove only its saved density preference when possible.

Separate the interface change from persistence

Reset means “default now” and “no saved key”

Reset has two separate jobs. First, apply DEFAULT_DENSITY to the in-memory state so the Reading Room immediately shows its default density. Then try to remove only STORAGE_KEY from browser storage.

A reset is not a save of the default. Saving "comfortable" leaves a stored value behind; removing the key means the next successful read finds no saved preference and uses the default.

What a reset changes

Diagram showing the Reading Room returning to Comfortable while only its density storage key is removed and an unrelated key remains.

The interface uses the default immediately. Storage removal targets one named preference, never all browser storage.

Attempt removal without blocking reset

Guard the removal helper

Put both the localStorage property access and the method call inside try. If either operation throws, the helper reports failure instead of interrupting the reset interaction.

javascript
function removeSavedDensity() {
  try {
    window.localStorage.removeItem(STORAGE_KEY);
    return true;
  } catch {
    return false;
  }
}

Handle the Reset preference button

The handler changes in-memory state before attempting removal. It renders after either result, so the selector and preview remain usable even when persistence is blocked.

javascript
const resetButton = document.querySelector(".reset-preference");

resetButton.addEventListener("click", () => {
  state.density = DEFAULT_DENSITY;
  const removed = removeSavedDensity();
  state.feedback = removed
    ? "Default applied; saved preference removal completed."
    : "Default applied for this page; saved preference could not be removed.";
  render();
});

Tip

Keep feedback accurate

If removal fails, the current page still displays the default. However, an older saved density may return after a reload because the key could not be removed. The failure message must describe that limit truthfully.

Practice: reset on both outcomes

Complete the reset flow

Complete the reset handler.

javascript
const resetButton = document.querySelector(".reset-preference");

resetButton.addEventListener("click", () => {
  state.density = ;
  const removed = removeSavedDensity();
  state.feedback = removed
    ? "Default applied; saved preference removal completed."
    : "Default applied for this page; saved preference could not be removed.";
  
});

Step 7 of 8

Interpret browser limits and test failures

Interpret persistence outcomes by browsing context and plan reversible failure checks for the guarded Reading Room helpers.

Treat persistence as context-dependent

Context changes the result

A result belongs to its browsing context

localStorage is not a universal folder for every copy of a page. For an HTTP or HTTPS page, storage is associated with its origin: scheme, host, and port. A different origin—or a different browser profile—uses a different storage context.

If you open index.html directly, it has a file: URL. Local-file storage behavior is not standardized as a reliable persistence workflow. If the Reading Room choice survives a reload there, that is an observation in your current browser, not a portability guarantee.

Private browsing can permit temporary storage, and browser policies or settings can block storage. Neither has one universal outcome.

Storage contexts are separate

Four isolated browser contexts each contain a similar Reading Room interface, showing that their saved preferences are separate.

A saved preference is tied to its context. Another origin, profile, or local-file page may not see it.

Check without overgeneralizing

Separate save evidence from restore evidence

Trace a meaningful reload check

The complete Reading Room files arrive in Step 8. For now, learn how to interpret the check rather than running it.

A meaningful persistence check keeps the same page and browser context throughout:

  1. A reader changes density.
  2. The page reports whether the latest choice was saved or applies only to the current page.
  3. The reader reloads that same page without changing profile, origin, or browsing mode.
  4. The reader compares the restored selector and preview.

The status message is evidence about the save attempt. The state after reload is evidence about the later read and restore path. Treat them as separate observations.

One context, two kinds of evidence

A diagram shows a density change, a save outcome, a reload, and a later restore outcome within one browser context.

Read the status before reload as save evidence; read the restored interface after reload as restore evidence.

How to describe outcomes

Use the observed result to make a narrow claim—not a claim about every browser or context.

Interpret only the condition you actually tested in Step 8.
ObservationSound conclusion
Compact remains after reloadPersistence worked in this same context for this check.
Status says current page onlyThe latest choice remained in-memory; it was not confirmed as saved.
A local-file result persistsThis browser allowed it in this local-file check; portability is not guaranteed.
A private-window result differsThat mode behaves differently here; private browsing has no universal rule.

Exercise each guarded path later

Use the helper's existing recovery path

Plan a reversible failure simulation

In Step 8, after the complete files are in place, you can exercise each guarded recovery path without changing browser security settings. Temporarily add an explicit throw as the first statement inside the existing try block of the helper you want to trace. Its existing catch then handles that exception.

Place it in loadDensity() to trace the default after an unreadable saved preference. Place it in saveDensity(value) to trace a choice that remains usable only in in-memory state. Place it in removeSavedDensity() to trace a reset that shows the default without claiming removal succeeded.

A deliberate throw tests recovery logic; it does not demonstrate the browser's actual storage policy. Remove the throw after the check.

Illustrative write-failure placement

Study placement only; do not run this fragment until Step 8 provides the complete Reading Room files.

javascript
function saveDensity(value) {
  try {
    throw new Error("Simulated storage failure");
    window.localStorage.setItem(STORAGE_KEY, value);
    return true;
  } catch (error) {
    return false;
  }
}

Warning

Remove the simulation afterward

Keep the throw only long enough to trace one recovery path. Leaving it in place makes later operations fail deliberately and hides the browser's real behavior.

Apply a careful conclusion

Interpret and test the outcome

Hypothetical Step 8 outcome: You open index.html directly from disk in a private window. After choosing Compact, the status says the choice applies only to the current page. Reloading shows Comfortable.

Which conclusion and simulated-exception placement are sound?

Step 8 of 8

Build and verify a resilient preference

Build the complete Reading Room example locally, then verify valid restoration, safe fallbacks, and truthful behavior when browser storage cannot be used.

Build the visible interface

Assemble the Reading Room

Create a folder named reading-room anywhere on your computer. Add index.html, styles.css, and preferences.js, then save the complete contents from this step into the matching files. Open index.html in a browser after saving all three files.

This example needs no packages, assets, server, or downloads. It keeps one non-sensitive display choice: the density of a short Reading Room preview. The selector, preview, status region, and reset button are driven by the same in-memory state.

One state, two visible outputs

A comparison of comfortable and compact reading-preview layouts controlled by one density selector.

The selector and preview must always reflect the same in-memory density.

index.html

Save this as index.html.

html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Reading Room</title>
    <link rel="stylesheet" href="styles.css">
    <script src="preferences.js" defer></script>
  </head>
  <body>
    <main class="reading-room">
      <h1>Reading Room</h1>
      <p>Choose a display density for this page.</p>

      <section class="preferences" aria-labelledby="density-heading">
        <h2 id="density-heading">Display density</h2>
        <label for="density">Reading layout</label>
        <select id="density" name="density" aria-describedby="density-status">
          <option value="comfortable">Comfortable</option>
          <option value="compact">Compact</option>
        </select>
        <button class="reset-preference" type="button">Reset preference</button>
        <p id="density-status" class="preference-status" role="status"></p>
      </section>

      <article class="reading-preview">
        <h2>Today’s reading</h2>
        <p>A calm interface lets readers choose a layout that supports their attention.</p>
        <p>This preview makes the selected density visible immediately.</p>
      </article>
    </main>
  </body>
</html>
Expected output
Reading Room
Choose a display density for this page.

Display density
Reading layout: Comfortable
Reset preference

Today’s reading
A calm interface lets readers choose a layout that supports their attention.
This preview makes the selected density visible immediately.

This rendered result depends on saving this file together with the supplied `styles.css` and `preferences.js` in the same folder.

styles.css

Save this as styles.css.

css
:root {
  font-family: system-ui, sans-serif;
  color: #1f2933;
  background: #f4f7f8;
}

body {
  margin: 0;
}

.reading-room {
  width: min(42rem, calc(100% - 2rem));
  margin: 2rem auto;
}

.preferences,
.reading-preview {
  padding: 1.25rem;
  border: 1px solid #c9d3d8;
  border-radius: 0.75rem;
  background: #ffffff;
}

.preferences {
  display: grid;
  gap: 0.75rem;
}

select,
button {
  width: fit-content;
  padding: 0.5rem 0.75rem;
  font: inherit;
}

select:focus-visible,
button:focus-visible {
  outline: 3px solid #176b87;
  outline-offset: 2px;
}

.preference-status {
  min-height: 1.5rem;
  margin: 0;
}

.reading-preview {
  margin-top: 1rem;
  max-width: 38rem;
  font-size: 1.1rem;
  line-height: 1.8;
}

.reading-preview.is-compact {
  max-width: 32rem;
  padding: 0.8rem;
  font-size: 0.95rem;
  line-height: 1.4;
}
Expected output
Completed Reading Room appearance:
Comfortable: a white reading preview card with 1.25rem padding, 1.1rem text, and 1.8 line height.
Compact: the same preview with the is-compact modifier, 0.8rem padding, 0.95rem text, and 1.4 line height.

With all three supplied files saved, Comfortable has generous preview spacing. Choosing Compact makes the preview narrower with tighter padding, smaller text, and tighter line spacing.

Restore, change, and reset

Add the resilient behavior

Now add the complete preference lifecycle in preferences.js. The guarded reader validates before its result enters in-memory state. Change and reset handlers render whether or not storage succeeds.

Paste Part 1 first, then paste Part 2 directly below it in the same file. Save before reopening or refreshing index.html.

Guarded storage helpers

preferences.js — Part 1

javascript
const STORAGE_KEY = "reading-room:density";
const DEFAULT_DENSITY = "comfortable";

const densitySelect = document.querySelector("select[name=\"density\"]");
const readingPreview = document.querySelector(".reading-preview");
const resetButton = document.querySelector(".reset-preference");
const preferenceStatus = document.querySelector(".preference-status");

function loadDensity() {
  try {
    const value = window.localStorage.getItem(STORAGE_KEY);

    if (value === "comfortable" || value === "compact") {
      return value;
    }

    return DEFAULT_DENSITY;
  } catch (error) {
    return DEFAULT_DENSITY;
  }
}

function saveDensity(value) {
  try {
    window.localStorage.setItem(STORAGE_KEY, value);
    return true;
  } catch (error) {
    return false;
  }
}

function removeSavedDensity() {
  try {
    window.localStorage.removeItem(STORAGE_KEY);
    return true;
  } catch (error) {
    return false;
  }
}
Expected output
After the completed file loads:
A missing, empty, unsupported, or unreadable saved preference produces the supported default density: comfortable.
A saved "comfortable" or "compact" value is eligible to become the initial in-memory state.

This is the first part of one `preferences.js` file. Its visible behavior occurs after Part 2 is pasted below it and `index.html` loads the completed three-file set.

State, rendering, and events

preferences.js — Part 2

javascript
const state = {
  density: loadDensity(),
  feedback: ""
};

function render() {
  densitySelect.value = state.density;
  readingPreview.classList.toggle(
    "is-compact",
    state.density === "compact"
  );
  preferenceStatus.textContent = state.feedback;
}

densitySelect.addEventListener("change", () => {
  state.density = densitySelect.value;

  if (saveDensity(state.density)) {
    state.feedback = "Preference saved.";
  } else {
    state.feedback = "Density applied for this page only.";
  }

  render();
});

resetButton.addEventListener("click", () => {
  state.density = DEFAULT_DENSITY;
  const removed = removeSavedDensity();
  state.feedback = removed
    ? "Default applied; saved preference removal completed."
    : "Default applied for this page; saved preference could not be removed.";
  render();
});

render();
Expected output
Initial completed page:
The selector and preview agree on the restored supported density, or Comfortable when no supported value can be read.

After selecting Compact:
The selector shows Compact, the preview becomes compact, and the status is either:
Preference saved.
or
Density applied for this page only.

After Reset preference:
The selector and preview immediately show Comfortable, with feedback that accurately reports whether removal completed.

Paste this directly after Part 1 in the same `preferences.js` file. Exact reload persistence depends on whether the current browser context permits storage.

Verify normal and failure behavior

Prepare repeatable checks

Save the files and test in one browser context: the same browser profile, page location, and normal reload. You may use browser developer tools to prepare exact stored-value cases.

Paste the temporary helper into the Console. It changes only the Reading Room key and reports when the browser cannot use storage. Run one call from the matrix, then reload the page.

Prepare one stored-value case

Temporary Console helper

javascript
function prepareDensity(value) {
  try {
    if (value === null) {
      window.localStorage.removeItem("reading-room:density");
    } else {
      window.localStorage.setItem("reading-room:density", value);
    }
    console.log("Case prepared. Reload the page.");
  } catch (error) {
    console.log("Storage is unavailable in this context.");
  }
}
Expected output
Case prepared. Reload the page.

or, when the browser cannot perform the operation:

Storage is unavailable in this context.

Run the helper once in the browser Console, then call it with one value from the matrix. The second message indicates an environmental storage limit, not a successful preparation.

Manual check matrix

For each prepared case, reload index.html and compare the selector with the preview. A local file: URL may appear to persist in one browser, but that observation is not a portable guarantee. If the helper reports unavailable storage, still verify that changing density updates the current page.

Run a preparation call or action, reload where relevant, and observe the interface.
CaseConsole call or actionExpected after reload
Absent keyprepareDensity(null)Comfortable default
Supported valueprepareDensity("comfortable")Comfortable
Supported valueprepareDensity("compact")Compact
Obsolete valueprepareDensity("spacious")Comfortable default
Empty valueprepareDensity("")Comfortable default
ResetClick Reset preferenceDefault now; absent next reload if removal succeeds
Reload after a saved changeChoose a density, then reloadSame choice if saving succeeds

Example

Simulate failures reversibly

Test one recovery path at a time. Inside the existing try block of loadDensity(), saveDensity(value), or removeSavedDensity(), temporarily add this as the first statement:

throw new Error("Simulated storage failure");

For a read failure, refresh and confirm the default remains usable. For a write failure, change density and confirm the preview still changes while feedback does not claim a save. For a removal failure, click reset and confirm the default is shown without claiming removal. Delete the throw after each check and refresh.

A failed write or removal can allow an older saved preference to return after a later reload.

Summary

Final review

  • Validate a saved string before assigning it to in-memory state.
  • Restore before the first render; keep storage access out of render().
  • Treat persistence as optional: the current page remains usable after a caught storage exception.
  • A browser profile and origin affect persistence; local-file behavior is not a reliable cross-browser workflow.
  • Store non-sensitive preferences only, never confidential information.

Apply the full lifecycle

Document your resilient preference check

After saving the complete three-file example, submit a concise test record. Include the result after selecting a density and reloading; results for the missing-value case and both unsupported stored-value cases when storage was accessible; and what happened with simulated read, write, and removal failures. State whether every density change remained usable on the current page.

If storage was unavailable or reload persistence could not be observed, identify that as an environmental limit rather than an implementation failure. Name the browsing context you tested and explain the expected fallback: missing, empty, obsolete, or unreadable values use the default; a failed write or removal still leaves the latest in-memory choice usable for the current page.

Write at least 250 characters (0/250).

Tutorial complete

Congratulations! You completed: Save and Restore Browser Preferences

You assembled and verified a resilient browser preference: validated restoration, truthful persistence feedback, and usable in-memory behavior when storage is unavailable.

Download the App now to have access to + 5000 free courses, exercises, certificates and lots of content without paying anything!

  • 100% free online courses from start to finish

    Thousands of online courses in video, ebooks and audiobooks.

  • More than 60 thousand free exercises

    To test your knowledge during online courses

  • Valid free Digital Certificate with QR Code

    Generated directly from your cell phone's photo gallery and sent to your email

Cursa app on the ebook screen, the video course screen and the course exercises screen, plus the course completion certificate