What you'll learn
- Direct answer: keep state as close to the UI that needs it as you can
- When local, lifted, Context, reducer, server cache, or a store fits
- Common mistakes that create two sources of truth
- A short checklist before you add a library
Direct answer: put state next to the components that read and write it. Lift it only when siblings must share it. Reach for Context, a reducer, a server-state library, or an external store only when that placement fails.
This guide is the placement decision. For the full tool comparison, see State Management. For derived-state bugs, see Anti-Patterns.
When to use this
- You are about to add Context, Redux, or Zustand “just in case”
- Prop drilling feels annoying but you are not sure it is a real problem
- Server data and UI chrome are mixed in one store
When not to use this
- You already know the data is remote and need caching, retries, and dedupe: start with a server-state library
- You need SSR rendering strategy advice: see SSR, SSG, ISR, or CSR
Practical example
function SearchPage({ products }) {
// local: only this page cares about the query
const [query, setQuery] = React.useState('')
const visible = products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase())
)
return (
<>
<input value={query} onChange={(e) => setQuery(e.target.value)} />
<ul>
{visible.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</>
)
}query stays local. products come from the parent or a fetch cache. Do not copy products into state unless the user can edit them independently.
Decision guide
| Kind of data | Prefer |
|---|---|
| Input value, open/closed, tab index | Local useState |
| Shared by a few siblings | Lift state to the nearest parent |
| Rarely changing theme or locale | Context |
| Complex transitions with many next states | useReducer (local or in a store) |
| Remote list/detail with loading and cache | Server-state library |
| Many distant readers, frequent updates | External store (for example Zustand) |
Common mistakes
- Syncing props into state with
useEffectwhen you could derive during render - Putting server responses in a global client store by default
- Creating Context for every prop that travels two levels
Checklist
- Can one component own this value?
- If shared, what is the nearest parent that already knows both sides?
- Is this a server mirror? Prefer a fetch/cache library.
- Will many unrelated screens update this often? Only then consider a store.
Related
Final recommendation
Default to local state. Lift narrowly. Treat server data as server state. Add Context or a store when sharing or update frequency proves local state is too awkward, not before.