state-management
State Management
Use this skill when the real question is:
What packet is this state, who should own it, and what tempting wrong owner should we avoid?
Do not start with a library debate. Start by naming one primary packet, then choose the smallest owner that matches its lifecycle.
Read references/ownership-packets-and-route-outs.md before handling a mixed or ambiguous request.
Read references/decision-matrix.md when the packet is clear and you need to compare likely owners.
Read references/handoff-boundaries.md when the request may actually belong to react-best-practices, api-design, debugging, ui-component-patterns, design-system, or responsive-design.
When to use this skill
- Decide whether a frontend problem is local UI state, shared subtree state, URL/navigation state, form state, server state, or long-lived client workflow state.
- Turn vague requests like “we need state management” into a concrete ownership recommendation.
- Choose between Context, Zustand, Redux Toolkit, Jotai, TanStack Query, or router-native ownership without treating them as interchangeable.
- Stop “one store for everything” proposals before implementation starts.
- Produce a short ownership brief for implementation or review.
When not to use this skill
- The main task is rerender churn, hydration mismatch, waterfalls, or client/server performance behavior → use
react-best-practices. - The main task is mutation shape, backend ownership, or optimistic-write API design → use
api-design. - The main task is debugging stale closures, races, broken optimistic updates, or existing state bugs → use
debugging. - The main task is controlled/uncontrolled component APIs, primitives, slots, or reusable component contracts → use
ui-component-patterns. - The main task is visual system governance or shared preference policy → use
design-system. - The main task is viewport adaptation or layout collapse → use
responsive-design. - The architecture choice is already made and the user just needs code → implement directly instead of re-running the chooser.
Instructions
Step 1: Pick one primary packet
Choose the main packet before naming any tool:
- local-ui — toggles, open panels, inline edit state, active row, one-feature transient state
- shared-subtree — theme, auth snapshot, locale, feature flags, moderate shell state
- url-navigation — filters, sort order, pagination, selected tab, deep-linkable view state
- form-lifecycle — dirty/touched state, validation, submission lifecycle, async submit errors
- server-state — fetched data, caching, invalidation, revalidation, optimistic server updates
- client-workflow — cross-page drafts, undo, multi-step coordination, offline edits, long-lived workflow state
Optional: list one or two secondary packets, but force a single primary packet.
Quick frame:
Primary packet: server-state
Secondary packets: url-navigation, local-ui
Decision pressure: shareable filters + remote list freshness
Step 2: Choose the smallest owner
Use the narrowest owner that matches the packet:
- local-ui → local state, lifted state, or
useReducer - shared-subtree → Context when one moderate tree needs a shared source of truth
- url-navigation → router/search params when refresh, deep links, and back/forward should work naturally
- form-lifecycle → form-layer ownership, not app-wide global state
- server-state → TanStack Query or router-native data APIs when cache/revalidation rules dominate
- client-workflow → Zustand, Redux Toolkit, Jotai, or explicit state-machine modeling only after the earlier packets are separated
Rule: if the state is really route-shaped or server-shaped, do not hide it in a generic client store just because the store already exists.
Step 3: Run the anti-pattern check
Call out the most likely wrong owner explicitly:
- one universal store for unrelated lifecycles
- mirroring server cache into a client store without an offline/workflow reason
- hiding shareable filters or tabs outside the URL
- pushing form dirty/touched/submit lifecycle into global app state
- treating every slow or buggy UI issue as a state-architecture problem
A good answer names both the chosen owner and the tempting wrong owner.
Step 4: Compare client-store options only if a client-workflow packet remains
After local/shared/URL/form/server packets are separated:
- Context when the real pain is access/distribution across a moderate tree
- Zustand when you need low-ceremony cross-component or cross-page workflow state
- Redux Toolkit when workflows are coupled, event-heavy, or need stronger conventions and auditability
- Jotai when fine-grained atom composition truly matches the app’s mental model
- State machines / explicit workflow modeling when transitions, guards, and multi-step coordination dominate
Do not compare stores for packets that already belong to URL/form/server ownership.
Step 5: Name mixed architectures directly
Healthy combinations are normal:
- URL state + query/router data + local UI state
- Context + query/router data
- Zustand + query/router data
- Redux Toolkit + query/router data
- form state + URL state + query/router data
If the request asks for one universal store, explain what gets worse when unlike lifecycles are flattened together.
Step 6: Route adjacent work out immediately
Stay here only while choosing ownership.
Route out when the next step is:
react-best-practicesfor rerender/hydration/waterfall/perf issuesapi-designfor mutation contracts or backend/client responsibilitydebuggingfor already-broken state behaviorui-component-patternsfor component API ownershipdesign-systemfor system-level preference/governance rulesresponsive-designfor layout/viewport behavior
Step 7: Produce one ownership brief
Use this format:
Primary packet:
Secondary packets:
Recommended owners:
- ...
Why this is the smallest viable split:
- ...
Avoid:
- ...
Route-out note:
- ...
Next implementation step:
- ...
Keep it short. If the honest answer is “do less and keep the state closer to where it lives,” say that directly.
Output format
Return a concise ownership recommendation with:
- one primary packet and any secondary packets
- chosen owners per packet
- one-paragraph rationale
- explicit anti-patterns to avoid
- route-out note if another skill should own the next step
Examples
Example 1: Dashboard data, filters, and modals
Input: “Should this dashboard use Zustand or TanStack Query for project data, filters, and modal state?”
Good output shape:
- primary packet = server-state
- secondary packets = url-navigation, local-ui
- project data = TanStack Query or router-native data ownership
- filters = URL/search params if shareable/bookmarkable
- modal state = local or lightweight client state only if coordination spans distant branches
- avoid mirroring fetched entities into Zustand without an offline/workflow reason
Example 2: Prop drilling theme and auth
Input: “Theme and auth are passed through five levels. Do we need Redux?”
Good output shape:
- primary packet = shared-subtree
- recommend Context first
- reject Redux unless broader coupled workflow state also exists
Example 3: React Router app storing filters and loader data in Zustand
Input: “Our React Router app stores filters and loader data in Zustand. Is that fine?”
Good output shape:
- primary packet = url-navigation or server-state, not client-workflow
- move shareable filters into URL/search params
- keep loader/action data in router-native data ownership unless a separate cache need is real
- use Zustand only for leftover workflow coordination
Example 4: Broken optimistic updates
Input: “The state code is buggy and optimistic updates are broken—what skill owns the next step?”
Good output shape:
- identify that ownership choice is secondary to active failure diagnosis
- route the next step to
debugging - mention
api-designif the optimistic-write contract itself is the real dispute
Best practices
- Pick the packet before the tool.
- Use the smallest viable owner.
- Keep URL/form/server/client workflow lifecycles separate unless there is a strong reason not to.
- Call out the wrong owner explicitly.
- Treat mixed ownership as normal, not as a failure to standardize.
- End with a brief that an implementer or reviewer can act on immediately.