← Teacher guide

Saving between visits

Students predict a visit counter after three runs, save a reading list as JSON so it survives Run, find a list saved without JSON.stringify, make Start again forget the saved copy, remember a name, and keep a best score.

The DOM and the browser console · Final challenge: a quiz page · about 50 minutes · Open the student lesson

Classroom resources

Load these into Waypoints Prepare: choose Load URL and paste the copied address, or download the file and import it. In Prepare, the worksheet's Teacher edition shows the answers; the student copy hides them.

Students will be able to

Timing

Predict and Run 10 min · Investigate 15 min · Modify 10 min · Make 15 min

Before class

An optional lesson at the end of the DOM course, and the "saving" step for every route through the Web pathway. localStorage is a small store each browser keeps per website: setItem(name, text), getItem(name) (null when nothing is saved) and removeItem(name). It keeps text only, so lists and objects go through JSON.stringify and JSON.parse (met in the fetch lesson). The planted bug saves a list without stringify: the first run looks fine, the second fails, because the bad text is still saved. The playground's hint says to clear it.

The pattern for any app: load once when the page opens, save every time the data changes. In the playground, each Run is a new visit; the playground keeps each step's saved data for this browser (the preview page cannot use the real store, so a stand-in behaves the same way). On a real site it is the browser's own localStorage, per site and per browser: nothing is shared between computers or people, and clearing browsing data deletes it. Don't save anything private.

For Vue students (the to-do app): load the list in setup (const tasks = ref(JSON.parse(localStorage.getItem("tasks")) || [])), then watch(tasks, (t) => localStorage.setItem("tasks", JSON.stringify(t)), { deep: true }). Preact and Svelte have their own versions (useEffect, $effect).

Running the lesson

  1. Predict: ask what happens to variables when a page closes.
  2. Run: after Start again and Run, ask where the books came back from.
  3. Investigate: draw two boxes on the board, the page's list and the saved text, and an arrow each way.
  4. Modify: ask where in the code the saved data changes, and whether each place saves.
  5. Make: ask when the best score must be loaded and when saved.

The hard part (on purpose)

Students forget that saving is separate from the page: they change the list and expect it saved. Ask "which line saves it?" after every change they make.

When students get stuck

What you will seeA question to askThe fix
Red error: is not valid JSON.Was the list saved with JSON.stringify?Save with JSON.stringify; the bad copy is still saved, so remove it once (localStorage.removeItem) and run again.
The data is gone after Run.Does the code save it when it changes, and load it at the start?setItem after every change; getItem when the page opens.
A saved number adds like text (1 + 1 = 11).What does getItem give back?Text: turn it into a number with Number(...).

Answers to the checks

  1. Predict: You press Run three times. What does the page show after the third?
    Visits: 3. Each run reads the saved number, adds one and saves it. The variable starts over; the saved text does not.
  2. Which line does what?: Match each line with what it does.
    const saved = localStorage.getItem("books"); → D. loads the saved text, or null if there is none; books = JSON.parse(saved); → C. turns the saved text back into a list; localStorage.setItem("books", JSON.stringify(books)); → B. saves the list as text, every time it changes; if (saved !== null) { … } → A. keeps the empty list on the very first visit
    Load once, when the page opens; save every time the data changes. JSON is the bridge between a list and text.
  3. Start again: Start again empties the list on the page. After you press Run, the books are back. Why?
    Start again never changed what was saved, so the next visit loads the old list
    The page and the saved copy are separate. Start again must also save the empty list, or remove the saved one.
  4. Spot the bug: The first Run shows 1 books. The next Run shows a red error: "Book 1" is not valid JSON. Click the line that is wrong.
    line 9
    setItem saved the list as plain text (Book 1), not as JSON, so JSON.parse can't read it next time. Save it with JSON.stringify(books).
  5. Where this leads: Your to-do app saves its list with localStorage. A friend opens the same page on their own computer. What do they see?
    Their own list (empty at first): each browser keeps its own data
    localStorage stays in one browser on one computer. Sharing data between people needs a server, as in Web apps with Flask.

Solutions

One possible solution for each task. Other correct programs are fine.

Modify › Really start again

<h1>Books I have read</h1>
<input id="title" placeholder="A book">
<button id="add">Add</button>
<ul id="books"></ul>
<button id="clear">Start again</button>

<script>
  const list = document.querySelector("#books");
  let books = [];
  const saved = localStorage.getItem("books");
  if (saved !== null) {
    books = JSON.parse(saved);
  }

  function show() {
    list.textContent = "";
    for (const title of books) {
      const li = document.createElement("li");
      li.textContent = title;
      list.append(li);
    }
  }

  document.querySelector("#add").addEventListener("click", function () {
    const box = document.querySelector("#title");
    books.push(box.value);
    localStorage.setItem("books", JSON.stringify(books));
    box.value = "";
    show();
  });
  document.querySelector("#clear").addEventListener("click", function () {
    books = [];
    localStorage.removeItem("books");
    show();
  });
  show();
</script>

Modify › Remember a name

<h1>Books I have read</h1>
<input id="title" placeholder="A book">
<button id="add">Add</button>
<ul id="books"></ul>
<button id="clear">Start again</button>
<p>Reader: <input id="reader"></p>

<script>
  const list = document.querySelector("#books");
  let books = [];
  const saved = localStorage.getItem("books");
  if (saved !== null) {
    books = JSON.parse(saved);
  }

  function show() {
    list.textContent = "";
    for (const title of books) {
      const li = document.createElement("li");
      li.textContent = title;
      list.append(li);
    }
  }

  document.querySelector("#add").addEventListener("click", function () {
    const box = document.querySelector("#title");
    books.push(box.value);
    localStorage.setItem("books", JSON.stringify(books));
    box.value = "";
    show();
  });
  document.querySelector("#clear").addEventListener("click", function () {
    books = [];
    show();
  });
  const reader = document.querySelector("#reader");
  if (localStorage.getItem("reader") !== null) {
    reader.value = localStorage.getItem("reader");
  }
  reader.addEventListener("input", function () {
    localStorage.setItem("reader", reader.value);
  });
  show();
</script>

a best score that lasts

<p>Clicks: <span id="count">0</span> · Best: <span id="best">0</span></p>
<button id="click">Click me</button>
<button id="new">New game</button>

<script>
  // Save the best score, so it is still there after Run (or a visit next week).
  let clicks = 0;
  let best = Number(localStorage.getItem("best"));
  const countEl = document.querySelector("#count");
  const bestEl = document.querySelector("#best");
  bestEl.textContent = best;
  document.querySelector("#click").addEventListener("click", function () {
    clicks = clicks + 1;
    countEl.textContent = clicks;
    if (clicks > best) {
      best = clicks;
      bestEl.textContent = best;
      localStorage.setItem("best", best);
    }
  });
  document.querySelector("#new").addEventListener("click", function () {
    clicks = 0;
    countEl.textContent = clicks;
  });
</script>

Stretch and support

Stretch: Save the to-do app from Apps with Vue (see the note above). Save a whole object (name and score) as one JSON item.

Support: Give the load and save lines on the board; students choose where each goes.

Exit ticket

Why must a list be saved with JSON.stringify?

localStorage keeps only text; JSON.stringify turns the list into text that JSON.parse can turn back into a list.

What each step is for

Where it leads

Next on the Web pathway: Your site, for real (accessibility, then putting the page online). Sharing saved data between people needs a server: Web apps with Flask.

Sources