// normalised
{
users: { byId: { u1: { id: "u1", name: "Ana" } } },
posts: { byId: { p1: { id: "p1", authorId: "u1" } }, feedIds: ["p1"] }
}
Normalised state stores each entity once, keyed by its id. Lists and relations hold only ids. So if a user changes their name, only one record changes. Every post, comment and avatar that points to that user then updates.
Apollo and Relay normalise automatically, using __typename and id. In Redux you use something like entity adapters.
A query-keyed cache stores each response as one blob under its key. It is simpler and needs no knowledge of the schema. But the same entity can be duplicated across keys. So after a mutation, you either update several keys, or invalidate them and refetch.
Normalisation is worth it when the same entities appear in many views and are edited in place. Examples:
- a chat app
- a project tool with tasks shown on boards, lists and detail panels
- a collaborative app that receives real-time patches
For mostly read-only pages, a query cache plus targeted invalidation means less code and fewer bugs.
Normalisation has costs:
- more setup
- GC (garbage collection: removing entities that nothing uses any more)
- handling partial objects, where a view loaded only some of an entity's fields
- Each entity is stored once by id, and lists hold only ids.
- One update to an entity shows up in every view.
- A query cache can duplicate entities and relies on invalidation.
- Normalisation is worth it for shared entities that are edited often.
- Its costs are setup, garbage collection of unused entities, and partial objects.