The store is one source of truth, or it is not a source of truth
“Single source of truth” gets repeated so often it sounds like a slogan, but it is a hard constraint with teeth. It means every fact your app knows lives in exactly one place, and everything else derives from it or reads it — never copies it. The moment the same fact exists in two places, you have signed up for a job you cannot do reliably: keeping them in sync on every change, forever. The bugs that follow — a badge showing 3 while the list shows 4, a form that remembers a value the server already changed — are not careless mistakes. They are the guaranteed outcome of duplicated state.
Duplication is a synchronisation contract you signed by accident
Say you keep a list in the store and also keep its length in a count field.
You have quietly promised that every code path touching the list also updates the
count. Miss one — a filter, a bulk delete, an optimistic update that rolls back —
and they disagree, with no way to tell which is right:
// TWO sources: count can drift from items.length
state = { items: [...], count: 5 };
// DERIVE instead: count is always correct because it is computed, not stored
const count = state.items.length; // one fact (items), one derivation (count)
The fix is never “add code to keep them in sync.” It is “stop storing the second one.”
Server data is a copy too — name it as such
The subtlest duplication is caching server data in a client store and then editing the cached copy as if it were the truth. Now the truth lives on the server and in your store, and they diverge the instant another user changes the record. The honest model is that your store holds a cached view of server state, with its own freshness, and the request/success/fail cycle is how you reconcile it:
// the store is explicit that this is a cache of a remote fact, not the fact
case "USER_FETCH_SUCCEEDED":
return { ...state, user: action.data, fetchedAt: action.at };
Minimise, normalise, derive
Three habits keep the store a genuine single source of truth. Minimise: store only irreducible facts. Normalise: store each entity once, by id, and reference it elsewhere rather than embedding duplicate copies. Derive: compute everything else with selectors on read. Together they mean there is exactly one place to change any given fact, so there is exactly one thing to get right. The simple-store exercise builds the dispatch-reducer-subscribe loop from scratch, which is the clearest way to see why one store, read by everyone, is the only arrangement that cannot silently disagree with itself.