Code

js/state.js — pub/sub and persistence

In the code column — js/state.js. At last state has a home: one module that holds everything and is the only thing allowed to change it. Lesson 8's scattered variables gather here.

Three parts:

  • Pub/sub: the listeners Set, subscribe(fn) (adds and returns an unsubscribe), notify() (calls everyone). That's the whole mechanism.
  • Persistence: loadFavorites() reads from localStorage at startup, persistFavorites() writes after every change.
  • Actions: toggleFavorite changes favorites, saves, and calls notify(). The only way to change state is through an action like this.

Notice the guard — this is real production maturity:

function loadFavorites() {
  try {
    const raw = localStorage.getItem(FAVORITES_KEY);
    return raw ? JSON.parse(raw) : [];
  } catch {
    return [];                     // corrupt JSON → start clean, don't crash
  }
}

function persistFavorites() {
  try {
    localStorage.setItem(FAVORITES_KEY, JSON.stringify(state.favorites));
  } catch (e) {
    console.warn('Could not save favorites:', e);   // quota / private browsing
  }
}

localStorage is no guarantee. On read, the JSON might be corrupt (hand-edited, an old version). On write, the quota (~5 MB) might be exhausted, or private browsing disables it entirely. We catch both: on read we fall back to an empty list, on write we just warn. Degrade, don't die: the app keeps working in memory, the user only loses persistence.

Tip. getState() returns state for reading only. The convention: state changes go only through actions (like toggleFavorite) that end in notify(). If you mutate state.favorites directly somewhere without notify(), you're back to lesson 8's disease — the views won't update. A single entry point for changes is what the whole order rests on.