React

How do you decide between React state, context, and external state managers?

The short answer

Start with local state (useState or useReducer). If multiple components at different levels need the same data and it doesn't change frequently, use React context. If you have complex, frequently updating state that many components subscribe to, reach for an external state manager like Zustand, Jotai, or Redux. And if the data comes from a server, treat it as a separate concern entirely using a library like React Query or SWR.

The guiding principle is simple: use the simplest tool that solves your problem, and escalate only when you hit a real limitation.

Local state — the default choice

useState and useReducer are where you should start. If a piece of data is only needed by one component or a small subtree, keep it local.

function SearchBar() {
const [query, setQuery] = useState('');

return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
);
}

The search query lives in SearchBar because that's the only component that needs it. There's no reason for the rest of the app to know about it.

When two sibling components need to share state, lift it to their common parent. This is React's bread-and-butter pattern and handles a surprising number of cases.

function ProductPage() {
const [selectedSize, setSelectedSize] = useState('M');

return (
<div>
<SizeSelector
selected={selectedSize}
onChange={setSelectedSize}
/>
<AddToCartButton size={selectedSize} />
</div>
);
}

Both SizeSelector and AddToCartButton need the selected size, so it lives in ProductPage. Simple, explicit, easy to trace.

Context — for low-frequency, widely-needed data

React context is designed for data that many components at different nesting levels need. The classic examples are theme, authentication, and locale:

const ThemeContext = createContext('light');

function App() {
const [theme, setTheme] = useState('light');

return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Header />
<Main />
<Footer />
</ThemeContext.Provider>
);
}

function NavButton() {
const { theme } = useContext(ThemeContext);
return (
<button
className={
theme === 'dark' ? 'btn-dark' : 'btn-light'
}
>
Menu
</button>
);
}

Context eliminates the need to pass theme through every intermediate component. Any component in the tree can read it directly.

But here's the catch: when a context value changes, every component that consumes that context re-renders. There's no built-in way to subscribe to only part of the context value. If you put fast-changing data into context (like a search input value that updates on every keystroke), every consumer re-renders on every keystroke.

This makes context ideal for values that change infrequently — theme toggles, user authentication state, locale preferences. It's less suited for high-frequency updates.

External state managers — for complex, high-frequency state

When your state is complex, updated frequently, and consumed by many components scattered across the tree, external state managers shine. They offer something React's built-in tools don't: fine-grained subscriptions.

With a library like Zustand, a component only re-renders when the specific slice of state it subscribes to changes:

import { create } from 'zustand';

const useStore = create((set) => ({
count: 0,
name: 'Guest',
increment: () =>
set((state) => ({ count: state.count + 1 })),
}));

function Counter() {
const count = useStore((state) => state.count);
return <p>{count}</p>;
}

function Greeting() {
const name = useStore((state) => state.name);
return <p>Hello, {name}</p>;
}

When count changes, only Counter re-renders. Greeting doesn't care about count and stays untouched. With context, both would re-render because they share the same provider.

Other popular options include:

  • Redux — the original. Heavy setup but powerful for large apps with complex update logic.
  • Jotai — atomic state management. Each atom is a tiny, independent piece of state.
  • Zustand — minimal API, no providers, works outside of React too.

Server state — a different beast entirely

Here's a mistake many developers make: putting server-fetched data into Redux or context. Server state (data from your API) has completely different needs than client state (UI toggles, form inputs, selections).

Server state needs caching, background refetching, stale-while-revalidate, pagination, optimistic updates, and deduplication. Libraries like React Query and SWR handle all of this out of the box:

function UserProfile({ userId }) {
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () =>
fetch(`/api/users/${userId}`).then((res) =>
res.json()
),
});

if (isLoading) return <Spinner />;
return <h2>{user.name}</h2>;
}

React Query manages the loading state, caches the response, refetches when needed, and shares the data across components that request the same query. You don't need Redux for this.

The decision framework

Here's a practical way to think about it:

  1. Start local. If the state is only needed by one component or its immediate children, use useState or useReducer.
  2. Lift up as needed. When siblings need to share state, move it to the nearest common parent.
  3. Use context for low-frequency, widely-needed data. Theme, auth, locale — things that rarely change and many components read.
  4. Use an external manager for high-frequency, complex state. Shopping carts, real-time dashboards, collaborative editing — when you need fine-grained subscriptions and complex update logic.
  5. Use a server state library for server data. API responses, pagination, and anything that needs caching and synchronization.

The most common mistake is reaching for a global solution too early. Not every piece of state needs to be globally available. A modal's open/closed state, a form's field values, a dropdown's selected item — these are local concerns. Keeping them local makes components self-contained, easier to test, and simpler to reason about.

Why interviewers ask this

This question reveals your architectural judgment. Anyone can learn the API for Redux or Zustand. Interviewers want to know if you can make the right choice for the right situation — and more importantly, if you understand the tradeoffs. The strongest answers show a clear mental model: start simple, escalate when you hit a concrete problem, and recognize that different kinds of state have different needs.