Use a short checklist, from top to bottom:
- Does it come from the server? Put it in the server cache, keyed by the query.
- Must it survive a reload or be shareable? Put it in the URL. Examples are filters, tabs, the selected item and the page.
- Otherwise, keep it as local as possible, with
useStatein the one component that needs it.
If sibling components need it, lift it to their nearest common parent.
If many distant components need it and it changes rarely, context is fine. Examples are the theme, the locale (the user's language and region) and the current user.
If it changes often and many components read parts of it, use a store with selectors, like Zustand or Redux. A selector lets a component subscribe to only the part it needs. Examples are an editor document or a complex multi-step wizard. Selectors avoid re-rendering every consumer on each change.
Also avoid duplicated or derived state. If you can compute a value from props or other state, compute it during render. Do not store a copy and try to keep it in sync.
- Server data goes in the server cache.
- State that must be shareable or survive a reload goes in the URL.
- Default to local state, and lift it up only when it is needed.
- Use context for state that changes rarely, and a store with selectors for state that changes often.
- Compute derived values during render instead of storing duplicates.