IntermediateJavaScript · Lesson 8 of 10

Saving Data: localStorage & JSON

Remember settings and data between visits with localStorage, safely converting to and from JSON.

localStorage stores text in the user's browser, per website, and keeps it after the page is closed. sessionStorage works the same way but is cleared when the tab closes. Both hold only strings, so convert objects with JSON.stringify and back with JSON.parse.

Stored data can be missing, outdated or corrupted, and storage can be blocked (private browsing, full quota). Wrap reads and writes in try/catch and fall back to sensible defaults.

Use localStorage for preferences, drafts and small app data. Never store passwords, tokens or personal data there — any script on the page can read it.

index.htmlHTML
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Preferences</title>
    <style>
      body.dark { background: #0e1826; color: #e7ecf6; }
    </style>
    <script src="app.js" defer></script>
  </head>
  <body>
    <button id="theme">Toggle dark mode</button>
    <label>Draft note <textarea id="draft" rows="3"></textarea></label>
    <p id="visits"></p>
  </body>
</html>
Live preview in a sandboxed frame
app.jsJavaScript
const KEY = "dolese.prefs.v1";
const DEFAULTS = { theme: "light", draft: "", visits: 0 };

function loadPrefs() {
  try {
    const raw = localStorage.getItem(KEY);
    return raw ? { ...DEFAULTS, ...JSON.parse(raw) } : { ...DEFAULTS };
  } catch {
    return { ...DEFAULTS };      // corrupted JSON or storage blocked
  }
}

function savePrefs(prefs) {
  try {
    localStorage.setItem(KEY, JSON.stringify(prefs));
  } catch (err) {
    console.warn("Could not save preferences:", err);
  }
}

const prefs = loadPrefs();
prefs.visits += 1;
savePrefs(prefs);

const draft = document.querySelector("#draft");
document.body.classList.toggle("dark", prefs.theme === "dark");
draft.value = prefs.draft;
document.querySelector("#visits").textContent = `You have visited ${prefs.visits} time(s).`;

document.querySelector("#theme").addEventListener("click", () => {
  prefs.theme = prefs.theme === "dark" ? "light" : "dark";
  document.body.classList.toggle("dark", prefs.theme === "dark");
  savePrefs(prefs);
});

draft.addEventListener("input", () => {
  prefs.draft = draft.value;
  savePrefs(prefs);
});

Key points

  • localStorage keeps strings per site; use JSON to store objects.
  • Always handle missing or corrupted data and blocked storage.
  • Never store secrets or personal data in localStorage.

Exercise

Add a "font size" setting (small / normal / large) saved in preferences. Add a "Reset preferences" button that clears the key with localStorage.removeItem and reloads the defaults.

Show solution

Try the exercise yourself first — then compare your approach with this one.

The font size becomes another preference: a <select> changes it, a class on <body> applies it, and it's saved with the others. Reset removes the stored key, restores the defaults and re-applies them.

index.htmlHTML
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Preferences</title>
    <style>
      body.dark { background: #0e1826; color: #e7ecf6; }
      body.size-small { font-size: 14px; }
      body.size-normal { font-size: 16px; }
      body.size-large { font-size: 20px; }
    </style>
    <script src="app.js" defer></script>
  </head>
  <body>
    <button id="theme">Toggle dark mode</button>
    <label>Font size
      <select id="font-size">
        <option value="small">Small</option>
        <option value="normal">Normal</option>
        <option value="large">Large</option>
      </select>
    </label>
    <button id="reset">Reset preferences</button>
  </body>
</html>
Live preview in a sandboxed frame
app.jsJavaScript
const KEY = "dolese.prefs.v1";
const DEFAULTS = { theme: "light", fontSize: "normal" };

function loadPrefs() {
  try {
    return { ...DEFAULTS, ...JSON.parse(localStorage.getItem(KEY) ?? "{}") };
  } catch {
    return { ...DEFAULTS };
  }
}

function savePrefs() {
  try {
    localStorage.setItem(KEY, JSON.stringify(prefs));
  } catch (err) {
    console.warn("Could not save preferences:", err);
  }
}

function apply() {
  document.body.classList.toggle("dark", prefs.theme === "dark");
  document.body.classList.remove("size-small", "size-normal", "size-large");
  document.body.classList.add(`size-${prefs.fontSize}`);
  fontSelect.value = prefs.fontSize;
}

const fontSelect = document.querySelector("#font-size");
let prefs = loadPrefs();
apply();

document.querySelector("#theme").addEventListener("click", () => {
  prefs.theme = prefs.theme === "dark" ? "light" : "dark";
  savePrefs();
  apply();
});

fontSelect.addEventListener("change", () => {
  prefs.fontSize = fontSelect.value;
  savePrefs();
  apply();
});

document.querySelector("#reset").addEventListener("click", () => {
  localStorage.removeItem(KEY);
  prefs = { ...DEFAULTS };
  apply();
});

Check your understanding

  1. What can localStorage store directly?

  2. How does sessionStorage differ from localStorage?

  3. Why wrap JSON.parse(localStorage.getItem(key)) in try/catch?

  4. Which of these should never go in localStorage?

Ask AI