State Management
Use this skill for state modeling first and library mapping second.
The goal is not to recommend a favorite library. The goal is to produce a state model that remains sound whether the implementation ends up in local component state, a reducer, Redux, Zustand, Pinia, URL params, TanStack Query, or no library at all.
Your job:
- reduce state to the smallest valid source of truth
- move derived values into selectors, getters, computed values, or render-time calculations
- replace boolean soup with explicit finite states when modes are mutually exclusive
- define state changes in terms of events and transitions rather than arbitrary setter calls
- place each piece of state in the right home
- review existing code for invalid states, duplication, synchronization bugs, and misplaced effects
- map the resulting model to the user's chosen library only after the model is clear
First pass
In an existing codebase:
- Inspect local
package.jsonfiles and imports. - Read the component, store, reducer, and selector/getter code near the problem.
- Identify which state is local, global, remote, URL-backed, or external.
- Separate current implementation details from the actual domain model before suggesting changes.
If the user wants a new design from scratch, model the domain directly and only then choose implementation.
Task mode
Choose a mode before answering:
- Design: build a new state model from requirements.
- Review: identify bugs, risks, invalid states, and misplaced state.
- Refactor: preserve behavior while simplifying the model and tightening invariants.
- Library mapping: translate an already-sound model into React, Redux, Zustand, Vue/Pinia, or plain functions.
Do not jump straight to API advice when the real problem is a broken model.
Core taxonomy
Classify every value before deciding where it belongs.
- Source state: authoritative facts the app must store.
- Derived state: values that can be computed from source state and props/input. Do not store these unless there is a proven boundary reason.
- Finite state: mutually exclusive modes such as
idle,editing,submitting,success,failure. - Server state: remote data with loading, freshness, cache, and invalidation concerns.
- URL state: values that should survive refresh, support sharing, or participate in back/forward navigation.
- Ephemeral mutable values: timers, DOM handles,
AbortControllers, previous snapshots, subscriptions, and other non-visual mutable values. - History state: undo/redo stacks, audit logs, previous states, and snapshots.
If a value does not clearly fit, you probably have not understood its role yet.
Core principles
- Keep source state minimal.
- Prefer one source of truth for each fact.
- Compute derived values instead of synchronizing them.
- Represent mutually exclusive modes with one finite
statusfield or a discriminated union, not several booleans. - Make invalid states hard or impossible to represent.
- Model updates as domain events and transitions.
- Keep effects at the boundary. Effects should react to state transitions, not serve as the primary coordination mechanism.
- Put state as low as possible, but as high as necessary.
- Distinguish client state from server cache and URL state.
- If several values always change together, model that relationship explicitly.
Workflow
Use this sequence unless the user explicitly asks for something narrower.
- Inventory all current or proposed state values.
- Classify each value using the taxonomy above.
- Remove duplication:
- delete mirrored props
- delete values that can be derived
- avoid storing both an object and its selected ID unless there is a clear cache boundary
- Define invariants:
- what combinations are valid?
- what combinations are impossible?
- what must always be true in each mode?
- Identify finite modes:
- where booleans actually describe one status, collapse them into a finite state
- List domain events:
- what can happen?
- who or what causes it?
- what data does the event carry?
- Sketch transitions:
- from which states is each event valid?
- what changes?
- what side effects happen at the boundary?
- Decide placement:
- local component state
- reducer/store
- URL
- server cache/query layer
- ref or external mutable object
- Map the model to the chosen library.
When requirements are fuzzy, show the model before the code.
Modeling questions
Use these questions before writing or revising code:
- What are the smallest facts we actually need to store?
- Which values are derived views of those facts?
- Which modes are mutually exclusive?
- What states are impossible but currently representable?
- What events can happen, and which ones are valid in each state?
- Does this state need to survive navigation or refresh?
- Is this really application state, or is it server cache?
- Is this value needed for rendering, or is it an ephemeral mutable handle?
- Who owns this state, and who is allowed to change it?
- Are we storing implementation details instead of domain truth?
- Are effects being used to synchronize state that should be derived?
Choosing the representation
Prefer the simplest representation that preserves invariants.
- Independent local values: use local state only when fields are actually independent.
- Finite modes: use a string literal status or discriminated union, not several booleans.
- Coordinated updates: use a reducer when several fields change together or event handling depends on current state.
- Complex workflows: use a state machine or actor model when there are guards, retries, cancellation, concurrency, or child processes.
- Relational collections: normalize entities when nested updates become awkward or inconsistent.
- Derived reads: use selectors, getters, computed values, or render-time calculations.
- Server data: prefer a query/cache tool instead of forcing remote lifecycle concerns into a UI store.
- Non-rendering mutable data: use refs or external mutable objects.
Do not recommend a more complex abstraction unless the simpler one cannot enforce the needed invariants.
Placement guidance
Choose where state lives by asking:
- Who reads it?
- Who writes it?
- How long must it live?
- Must it survive refresh?
- Must it be shareable by URL?
- Is it authoritative app state or remote cache?
Strong defaults:
- keep transient view-only state local
- keep cross-component client state in a reducer/store only when multiple consumers truly need it
- keep shareable navigation/filtering state in the URL
- keep fetched data in a server-state/cache layer
- keep non-visual handles out of render state
Anti-patterns
Flag these aggressively in reviews and refactors:
- derived state stored and resynchronized with effects
- mirrored props in local state
- many booleans describing one mode
- storing both
selectedItemandselectedItemIdwithout a clear reason - duplicated state across component, store, and URL
- action names that describe setters instead of domain events
- deep nested relational state that should be normalized
useEffectchains that coordinate business logic- putting most form state in a global store without a strong reason
- nullable field soup used instead of explicit states
- mixing server state, client state, and request lifecycle in one undifferentiated blob
Reference routing
Open only the relevant references for the current task:
- React:
references/react.md - Redux and Redux Toolkit:
references/redux.md - Zustand:
references/zustand.md - Vue and Pinia:
references/vue-pinia.md - Plain JavaScript or library-agnostic implementation:
references/no-library.md - TypeScript modeling and exhaustiveness:
references/typescript-modeling.md - Async lifecycle modeling:
references/async-state.md - Review pass and smell detection:
references/review-checklist.md
If the problem is specifically about XState or actor systems in a codebase that already uses them, prefer the dedicated xstate-v5 skill for implementation details.
Output format
Prefer this answer shape:
- State inventory: what exists now or what must exist.
- Findings or invariants: duplication, impossible states, and design constraints.
- Recommended model:
- source state
- derived state
- finite states or discriminated union
- events and transitions
- placement
- Library mapping: only if the user asked for implementation in a specific stack.
- Code: only after the model is clear enough.
For reviews, lead with concrete problems first. For design tasks, lead with the model first.
Final self-check
Before answering, verify:
- every stored value really needs to be stored
- derived values are not being mirrored as source state
- mutually exclusive modes are not represented as unrelated booleans
- invalid states are reduced or eliminated
- state ownership and placement are explicit
- effects are not doing the work of selectors or transitions
- async status is modeled explicitly
- the recommended abstraction is not more complex than the problem requires
- library-specific advice matches the user's actual stack