Data Modelling as a path for Faster UIs in React
A responsive React app often starts with a well-shaped state. When your data model lets you update one item without touching everything else, renders become cheaper, selectors are simpler, and bugs disappear. React’s shallow equality checks and memoization patterns work best when updates replace only the changed objects. A flat, predictable state shape makes that trivial. Poorly shaped state forces wide re-computation and broad re-renders — even when only one row in a table changed.
The first, and simplest principle is keeping state as close to where it is used. Hyper-local state like a modal open/close toggle or he values in a form should be local to the component that uses it. Careful component composition can then limit how much of the Virtual Dom tree gets re-rendered on a change. On the other hand, things like records fetched from the server are better placed in Global state. It may seem obvious, but it is very easy to fall in the trap of just placing everything in global state, regardless of if it is necessary. Regardless, this is foundational to any approach to handle state in a performant way.
The next step is normalizing state into flat entity maps backed by an ordered array of ids.
Storing your entities in this way enables updating one user (replacing only users.byId["u1"]) while keeping other references stable.
By maintaining the references of any unchanged parts of the state, equality checks in hooks like useEffect and useMemo will only trigger on changed entities.
Lastly, design for immutability. Making your updates shallow and predictable by replacing only what is on the change path makes React's shallow equality checks a non-issue. Always try to use small reducers/selectors that target specific slices; and when using hooks, ensure your dependencies/inputs are shallow-stable (will only change if a relevant change happened).
By mapping on the id array, the whole list will only re-render if the entity collection is refetched from the server, at which point re-rendering is often preferred in order to avoid weird desynchronization bugs. Otherwise, only the specific UserRow component which changed will re-render. While redux isn't strictly required, you can use other state management libraries, like Zustand, or even roll your own store with useExternalStore. React Context works against us in this case, as it doesn't support the kind of fine-grained selectors we are looking for.
Have you found success with other data modelling techniques? I would love to hear what you have found.