Theory

The cure: subscribe and notify

Remember the end of lesson 8. toggleFavorite had to re-render three views by hand:

renderGallery(...);      // the ♡ fill
renderCollection();      // the row
renderHome();            // the counter

And removeFromCollection forgot renderGallery() — so the ♡ stayed filled on a picture that was no longer in the collection. The screen silently lied. And the point wasn't carelessness: structurally, every action had to know every view, and nothing enforced it.

Here's the cure. Instead of an action calling each view, the state announces "I changed" once, and views react to it themselves. That's pub/sub (publish/subscribe), and the implementation is startlingly small:

const listeners = new Set();

export function subscribe(fn) {          // a view: "tell me when things change"
  listeners.add(fn);
  return () => listeners.delete(fn);     // return an unsubscribe function
}

function notify() {                       // an action: "I changed — update yourselves"
  listeners.forEach(fn => fn(state));
}

Now toggleFavorite collapses from three re-renders to one:

export function toggleFavorite(pic) {
  // ...change favorites...
  persistFavorites();
  notify();                              // ← that's it. Views update themselves.
}

The "forgotten re-render" bug becomes impossible — there's nothing left to forget. New actions (removeFavorite, updateFavorite) just call notify() and never mention a view.

The second theme is persistence. We keep favorites in localStorage: JSON.stringify to write, JSON.parse to read. But localStorage can fail (corrupt JSON, exhausted quota, private browsing disables it entirely), so we guard it — so the app doesn't crash, it just loses persistence.