Skip to content

TopicsIntermediate

Where Should State Live in a React Application?

Decide where React state should live: local UI state, lifted state, Context, reducers, server caches, or an external store.

Mohamed ElshahawyPublished Oct 2026
2 min read

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

JSX
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 dataPrefer
Input value, open/closed, tab indexLocal useState
Shared by a few siblingsLift state to the nearest parent
Rarely changing theme or localeContext
Complex transitions with many next statesuseReducer (local or in a store)
Remote list/detail with loading and cacheServer-state library
Many distant readers, frequent updatesExternal store (for example Zustand)

Common mistakes

  • Syncing props into state with useEffect when 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.

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.