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
listenersSet,subscribe(fn)(adds and returns an unsubscribe),notify()(calls everyone). That's the whole mechanism. - Persistence:
loadFavorites()reads fromlocalStorageat startup,persistFavorites()writes after every change. - Actions:
toggleFavoritechangesfavorites, saves, and callsnotify(). 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()returnsstatefor reading only. The convention: state changes go only through actions (liketoggleFavorite) that end innotify(). If you mutatestate.favoritesdirectly somewhere withoutnotify(), 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.