awesome-everything RU
↑ Back to the climb

Frontend Architecture

State shape: refactor the state soup

Crux Hands-on project — refactor a 'state-soup' filterable dashboard so every value lives in the right place: server cache, URL, colocated client state, or derived.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 240 min

Reading about shape mistakes is not the same as untangling them in a real app. Build (or take) a filterable list-plus-detail dashboard whose state is deliberately a mess — fetched data in useState, a stored count, filters lost on refresh, a global store re-rendering everything — and refactor each value into the right place, proving the win at every step.

Goal

Turn the unit’s decision tree into a working refactor: classify every piece of state, move server data to a cache library, shareable view-state to the URL, private state down to its lowest reader, delete what is derivable, and normalize the relational data you mutate — then prove fewer bugs and fewer re-renders.

Project
0 of 7
Objective

Take a 'state-soup' filterable dashboard (your own or the starter described below) where every value is in the wrong place, and refactor it so each piece of state lives in its correct shape — server cache, URL, colocated client state, or derived — with measurable proof that drift bugs and re-renders went down.

Requirements
Acceptance criteria
  • A before/after state inventory table showing each value's old home, new home, and the rule that moved it; nothing fetched lives in useState, nothing derivable is stored, and shareable view-state lives in the URL.
  • Refresh and copy-link both reproduce the exact filtered/sorted/paged view with the same detail open — demonstrated, not assumed.
  • A re-render count (React DevTools Profiler or a render-logging hook) before and after, showing typing in the filter or toggling a menu no longer re-renders unrelated subtrees such as the detail panel or charts.
  • A short write-up explaining, for each moved value, why its new home is correct and which shape bug (drift, lost view-state, re-render storm, stale snapshot) the move prevents.
Senior stretch
  • Add an optimistic mutation (rename/edit a record) through the cache library and show the list, badge, and open detail all stay consistent with no manual count or copy to update.
  • Replace ad-hoc query-string parsing with a typed URL-state layer (a small useSearchParams wrapper or a router's typed search params) so filters round-trip safely and invalid values fall back to defaults.
  • Add a 'shape lint' check to CI that fails if a fetched payload is stored in useState or if a known-derivable value (count, total) appears in state, to stop the soup creeping back.
  • Measure and report the network win: confirm the two reader components now issue one shared request instead of two, and that a filter change refetches once with dedupe rather than per-component.
Recap

This is the refactor you will run on real codebases: inventory every value, then route each to its correct home — fetched data to a server cache keyed by request, shareable view-state to the URL, private state down to its lowest reader, derivable values deleted, and mutated relational data normalized to a byId map. The proof is concrete: refresh and copied links reproduce the view, re-renders shrink to the owning subtree, and the drift bugs become impossible because there is no second source of truth left to drift. Doing it once on a toy dashboard makes the production untangle muscle memory.

Continue the climb ↑Where data fetching happens — and why it decides LCP
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.