skip to content

State and Context

How a React app decides where state lives, how it is shaped, and how it reaches the components that need it — colocation and lifting, reducers for complex transitions, context for sharing, and custom hooks for reuse. Interviewers push hard here because most React design questions ('how would you share this?', 'why does the whole tree re-render?') are really state-architecture questions.

part ofReactoverview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

When a React context provider is rendered with a new value, which components re-render because of that context change — every descendant of the provider, or only the ones that read the context?

level: juniorimportance: must knowfreq 62%

answer

  1. readers, not descendants
  2. two separate causes, same symptom
  3. memo compares props only
  4. depth does not matter for propagation
  5. Object.is on the value you passed

basics

~20 s

Only components that read that context re-render because of the change. React notifies the consumers it finds below the provider. Other descendants re-render for a different reason: their own parent re-rendered and re-created their elements.

solid answer

~40 s

A context change propagates only to consumers — components that call `useContext(MyContext)` or `use(MyContext)`. React compares the old and new value with `Object.is`; if they differ, it schedules work on every consumer beneath that provider, no matter how deep. Non-consuming descendants are untouched by that mechanism. What confuses people is that in practice the whole subtree often does re-render — but that is the ordinary cause: the component holding the state re-rendered, so it re-created its children's elements. Two separate causes with the same symptom. It also means `React.memo` does not protect a consumer: memo compares props, and a context read is not a prop, so a memoized consumer still re-renders when the value changes.

go deeper

for a junior

Be able to say plainly that a context update re-renders the components that read the context, at any depth, and that React decides by comparing the value it was given.

for a middle

Explain the two independent causes — provider value identity versus the owning component re-rendering — and why React.memo blocks the second but not the first.

for a senior

Show how you diagnose which cause is firing in a real app before changing anything, and pick the matching fix rather than sprinkling memo across the subtree.

for a principal

Frame it as an architecture question: where state lives determines how much of the tree is coupled to it, and a provider high in the tree is a decision about blast radius, not a rendering detail.

## The two independent reasons a component under a provider re-renders When you look at a React app and see "everything under the provider re-rendered", there are two different mechanisms that can produce that, and interviewers ask this question to see whether you can tell them apart. **Cause 1 — normal parent-to-child rendering.** The component that owns the state and renders the provider re-rendered. Rendering a component re-runs its function body, which re-creates the JSX elements it returns, which means React renders its children too (unless something bails out). This has nothing to do with context; it would happen with plain props. **Cause 2 — context propagation.** The provider was given a value that is not `Object.is`-equal to the previous one. React then walks the tree below that provider and schedules an update on each fiber that reads this particular context. Only readers are affected — a `<div>`, a layout wrapper, or any component that never calls `useContext` for that context is not a target of this mechanism. A reader is a component that calls `useContext(MyContext)` or, in React 19, `use(MyContext)`. The legacy `MyContext.Consumer` render-prop form and a class component's `contextType` also count as readers, though you rarely write them today. ## Why depth does not matter Context propagation is not parent-to-child hand-off. React does not need each intermediate component to re-render for a deeply nested consumer to receive the new value. That is the whole point of context: a consumer twenty levels down gets the update directly, even if every component in between bails out of rendering. This is also why `React.memo` is not a shield. `React.memo` makes a component skip re-rendering when its props are shallowly equal to last time. A context read is not a prop, so React marks the memoized component as needing work anyway: ```jsx const Badge = React.memo(function Badge() { const { user } = useContext(AuthContext); // still re-renders on value change return <span>{user.name}</span>; }); ``` What `React.memo` *can* do is stop **cause 1** from travelling through it. If a memoized component takes stable props and does *not* read the context, it bails out, and its own children are not re-rendered by the parent's render — even though a consumer further down still gets its context update through cause 2. ## Why the distinction is practical, not academic The fix you reach for depends on which cause you are looking at. If consumers re-render because the **value identity** keeps changing (a fresh object literal on every parent render), the fix lives at the provider: memoize the value, or split it up. If the subtree re-renders because the **provider's owner** re-renders, memoizing the value changes nothing about that subtree — you address it by not re-creating those children, for example by having the provider component render `children` it received as a prop rather than JSX it constructs itself. A quick way to check which one you are seeing: comment out the `useContext` call in a component that is re-rendering. If it still re-renders, you are looking at cause 1 and the provider value is not your problem. ## What "the value changed" means React's comparison is `Object.is` on the value you passed. Two structurally identical objects are different values: ```jsx // new object every render — every consumer re-renders every time <ThemeContext value={{ theme, toggle }}>{children}</ThemeContext> ``` A primitive value behaves the way people intuitively expect: passing the string `"dark"` twice in a row is the same value, so consumers are not notified. It is object and function values — the common case, because a context usually carries state plus updaters — that make this a pitfall. ## In React 19 You can render the context object directly as the provider, `<ThemeContext value={...}>`; `<ThemeContext.Provider value={...}>` still works and behaves identically. Reading with `use(ThemeContext)` is allowed inside conditionals and loops, unlike `useContext`. None of that changes the propagation model described above. ## Saying it in an interview "Context updates target readers, not descendants. React compares the value with `Object.is` and schedules work on every component that reads that context below the provider, at any depth, and `React.memo` doesn't stop it because a context read isn't a prop. If non-readers are also re-rendering, that's the ordinary parent-re-render path, which is a separate thing to fix."

  • If React.memo cannot stop a consumer from re-rendering, when is it still worth putting on a component inside a provider's subtree?
    It still blocks the ordinary parent-render path. A memoized component with stable props that does *not* read the context will bail out, so it and its non-consuming children skip rendering when the provider's owner re-renders. It just cannot block the context mechanism for a component that actually reads the context.
  • How would you tell, in a real app, whether a component is re-rendering because of the context or because its parent re-rendered?
    Remove or comment out the `useContext` call and see if it still re-renders — if it does, it is the parent path. The React DevTools Profiler is the non-invasive version: it labels why each component rendered, distinguishing a parent render from a context change.
  • Does an intermediate component between the provider and a consumer have to re-render for the consumer to get the new value?
    No. React schedules work directly on the consuming fibers, so the update reaches a deeply nested consumer even if every component in between bails out. That direct delivery is exactly what context exists to provide.

saying these in an interview costs you the question

  • Says every descendant of the provider re-renders
  • Thinks React.memo prevents context-driven re-renders
  • Believes the value has to be handed down through each level
  • Assumes context does a deep comparison of the value
  • Confuses a parent re-render with a context update

context

open as a page

Is React Context a state-management library? Explain what Context actually provides, and what it does not provide compared with an external store such as Redux or Zustand.

level: juniorimportance: must knowfreq 70%

basics

~20 s

React Context is a dependency-injection channel, not a state manager: it carries one value down the tree so descendants can read it without prop drilling. It stores nothing itself — the state still lives in useState, useReducer, or an external store.

open as a page

In React, when should logic inside a component be extracted into a custom hook, and when is a plain JavaScript function the better choice instead?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Extract a custom hook when the logic calls other hooks, or when the same hook wiring repeats across components. If the logic is a pure computation over its arguments, keep it a plain function outside the component.

open as a page

In React, two sibling components need the same value — one displays it and the other edits it. Where does that state go, and what does each sibling receive instead?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Move the state into the siblings' nearest common ancestor. That parent owns it with useState and passes the current value down to both children, plus a callback that lets the editing child ask for a change.

open as a page

A React component holds `firstName` and `lastName` in useState and keeps a third `fullName` state in sync with a useEffect that calls setFullName(firstName + ' ' + lastName). What is wrong with this, and what would you write instead?

level: juniorimportance: must knowfreq 72%

basics

~20 s

fullName is derivable, so storing it duplicates state. The effect runs after React commits, so the screen briefly shows a stale name and every keystroke costs a second render pass. Compute it during render as a plain const.

open as a page

A React component tracks a fetch with three useState booleans — isLoading, isError, isSuccess — plus data and error. Why is that considered a bug-prone way to model the state, and what do you model instead?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Three independent booleans allow eight combinations, and most are nonsense — loading and error both true, or success true with no data. Replace them with one status value (idle, loading, success, error) so contradictory states cannot be represented at all.

open as a page

In React, what makes data fetched from an API different from client state such as whether a dropdown is open, and why does that difference change how you store each?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Fetched API data is a local cache of state the server owns: shared with other users, able to go stale, needing refetch and invalidation. Client state such as an open dropdown is owned entirely by this browser tab and is never stale.

open as a page

Instead of exporting an AuthContext for callers to pass to useContext, many React codebases export only a custom useAuth() hook that throws when it finds no value. What does that pattern buy you, and how do you implement it?

level: middleimportance: must knowfreq 70%

basics

~20 s

The hook turns "you forgot the Provider" into an immediate, named error at the misuse site instead of a null that spreads through the app. Create the context with null, read it in the hook, throw if it is null, and export only the hook and the Provider.

open as a page

A React provider is written as `<AuthContext value={{ user, login, logout }}>{children}</AuthContext>` inside a component that re-renders often. Why does every consumer re-render even when user has not changed, and how do you fix it?

level: middleimportance: must knowfreq 78%

basics

~20 s

The object literal creates a new object on every render, so React sees a new context value each time and notifies all consumers. Wrap the value in useMemo keyed on its real dependencies, and keep the functions stable too.

open as a page

Two React components each call the same custom hook, useCounter(). If one component increments its counter, does the other component's value change? Explain what a custom hook does and does not share.

level: middleimportance: must knowfreq 66%

basics

~20 s

No. A custom hook shares logic, not state: every call site gets its own independent state, so the two counters are unrelated. Sharing one value requires lifting it to a common parent, putting it in context, or using an external store.

open as a page

Write a React `useDebouncedValue(value, delay)` hook using useState and useEffect. Then explain why a `useDebouncedCallback(fn, delay)` variant needs a ref holding the latest `fn`.

level: middleimportance: must knowfreq 62%

basics

~20 s

useDebouncedValue keeps a state copy updated by an effect that starts a timeout and clears it in cleanup, so each new value restarts the delay. A callback version must keep the newest fn in a ref, because a debounced wrapper created once would otherwise fire a frozen closure.

open as a page

A React component is written as `function Price({ amount }) { const [value, setValue] = useState(amount); ... }` and keeps showing the original number after the parent re-renders with a new `amount`. Why does the useState argument stop having any effect, and what are your options?

level: middleimportance: must knowfreq 78%

basics

~20 s

The argument to useState is only the initial value: React reads it on the component instance's first render and ignores it afterwards, so the copy freezes. Either derive from the prop directly, or treat the copy as genuinely separate state with an explicit reset.

open as a page

In React, a detail form keeps its edits in useState and stays mounted while the user switches from one record to another, so it still shows the previous record's values. Compare fixing this by giving the form a key derived from the record id, by adding a useEffect that resets the state when the id prop changes, and by calling an explicit reset action — which do you choose and why?

level: middleimportance: must knowfreq 62%

basics

~20 s

Prefer the key: rendering the form with key set to the record id makes React unmount the old instance and mount a fresh one, so every piece of state inside resets at exactly the right moment, with no reset code to keep in sync.

open as a page

Why must the reducer function you pass to React's useReducer be pure, and where do the side effects it might want to perform belong instead?

level: middleimportance: must knowfreq 58%

basics

~20 s

React runs reducers during rendering and may call one twice in development, so a reducer must only compute and return the next state from its arguments. Fetching, logging, storage writes, timers and randomness belong in the event handler or an effect.

open as a page

In React, what signals tell you a component should move from several useState calls to a single useReducer, and what do you actually gain by doing it?

level: middleimportance: must knowfreq 70%

basics

~20 s

Move to useReducer when several state fields change together on one event, when the next value depends on the previous one, or when the same update logic is repeated across handlers. You gain a single pure transition function you can test on its own.

open as a page

Two React components each fetch /api/user in their own useEffect and keep the result in their own useState. What problems does duplicating that server data create, and what changes if both read one shared cache entry instead?

level: middleimportance: must knowfreq 55%

basics

~20 s

Each copy fires its own request, ages independently, and is updated independently, so the two components drift apart and there is no single place to invalidate after a write. One entry keyed by the entity, shared by both readers, gives one request, one truth and one invalidation point.

open as a page

What concrete signals tell you that a piece of client state has outgrown React Context and belongs in an external store, and which signals mean you should stay with Context?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Move to a store when many consumers each need a different slice of frequently-changing state, when non-React code reads or writes it, or when you need middleware, devtools and persistence. Stay with Context for rarely-changing values consumed whole.

open as a page

In React, what is the argument you pass to createContext(defaultValue) used for, and in which situation does a component actually receive that default?

level: juniorimportance: should knowfreq 58%

basics

~20 s

createContext's argument is the value a consumer receives when no matching Provider sits above it in the tree. If a Provider is present but passes undefined, the consumer gets undefined — the default is not a general fallback.

open as a page

Write a React custom hook `useToggle(initial = false)` that returns a boolean and a function that flips it. Why should the flip call `setOn(v => !v)` instead of `setOn(!on)`?

level: juniorimportance: should knowfreq 52%

basics

~20 s

useToggle holds a boolean in useState and returns it with a toggler that calls setOn(v => !v). The updater form flips whatever the newest value is, so the toggle is still correct when the captured value is stale or two flips land in one event.

open as a page

In React, why is the dispatch function returned by useReducer safe to leave out of a useEffect dependency array, and what does that stability let you avoid elsewhere?

level: juniorimportance: should knowfreq 45%

basics

~20 s

React guarantees that the dispatch function keeps the same identity for the whole lifetime of the component, so listing it in a dependency array can never cause the effect to re-run. That stability also means dispatch needs no useCallback when passed to memoized children.

open as a page

A React tree renders <ThemeContext value="dark"> around the page and, deeper inside it, <ThemeContext value="light"> around a modal. What does a component inside the modal read, and what is nesting the same context deliberately good for?

level: middleimportance: should knowfreq 46%

basics

~20 s

It reads "light": React returns the nearest matching Provider above the reader, and values do not merge. Deliberate nesting scopes a value to a subtree — a local override, or a fresh independent instance of a Provider that owns state.

open as a page

In a React app where one context provides both the state and the function that updates it, why do teams split it into two separate contexts — one for the state and one for the updater — and what does that buy?

level: middleimportance: should knowfreq 52%

basics

~20 s

Components that only trigger updates do not need the state. Putting the updater in its own context gives it a value that never changes, so those write-only components stop re-rendering every time the state changes.

open as a page

In a React 19 app, code outside the component tree — an HTTP client interceptor, a WebSocket message handler, an analytics module — needs to read and update the current auth token. Why can React Context not serve that, and how would you structure it instead?

level: middleimportance: should knowfreq 45%

basics

~20 s

Context values are only readable from a component rendering under the provider, during render, so plain modules cannot reach them. Keep the token in a module-level store that any code can read and write, and expose it to components with useSyncExternalStore.

open as a page

When designing what a custom React hook returns, when should you return an array (a tuple, the way useState does) and when should you return an object?

level: middleimportance: should knowfreq 52%

basics

~20 s

Return a tuple for two or three positional values callers will frequently rename, as useState does. Return an object once there are several values or the set may grow, because names document meaning and adding a field breaks no existing caller.

open as a page

Implement a React `usePrevious(value)` hook that returns the value this component had on its previous render. Where must the ref be written, and what does the hook return the first time it runs?

level: middleimportance: should knowfreq 46%

basics

~20 s

Store the value in a useRef and overwrite it inside a useEffect that runs after every render, returning ref.current first. Reading before the effect writes yields the previous render's value; on the first render it is undefined.

open as a page

A React screen keeps every piece of state in the top-level page component, including a search string used only by one deeply nested table. What does that placement cost, and what rule decides where a piece of state actually belongs?

level: middleimportance: should knowfreq 56%

basics

~20 s

State belongs in the lowest component that needs it. Hoisting a table's search string to the page re-renders the whole screen on every keystroke, piles unrelated concerns into one component, and stops the table from being usable on its own.

open as a page

You are building a reusable React Tabs component. How do you let it manage the selected tab itself by default while still allowing a parent to own that state, and what does the component do differently in each mode?

level: middleimportance: should knowfreq 46%

basics

~20 s

Support both modes: seed internal state from a defaultValue prop, and treat the component as controlled whenever a value prop is supplied. Controlled, it renders the parent's value and only reports changes; uncontrolled, it updates its own state.

open as a page

A React table receives a `rows` prop and tracks the highlighted row with `const [selectedRow, setSelectedRow] = useState(null)` holding the whole row object. Why is storing the row's id usually the better model, and what breaks in the object version?

level: middleimportance: should knowfreq 54%

basics

~20 s

Storing the object duplicates data the rows prop already owns, so the stored copy goes stale when rows are refetched or edited. Store the id — the minimal fact React cannot compute — and look the row up during render.

open as a page

A React comment editor is rendered as <CommentBox key={draftText} authorId={authorId} /> so that it clears when the author changes, and now the textarea loses focus after every keystroke. What is wrong with that choice of key value, and what should it be instead?

level: middleimportance: should knowfreq 38%

basics

~20 s

The key is derived from content that changes while the component is alive, so every keystroke is a new identity and React remounts the editor, destroying the focused element. Key on the stable identity being edited, such as the author or comment id.

open as a page

A React component holds request state as { status, data, error } with status typed 'idle' | 'loading' | 'success' | 'error'. TypeScript still lets you read data while status is 'error'. How would you model the state so each status carries exactly the data that state has?

level: middleimportance: should knowfreq 45%

basics

~20 s

Model the state as a discriminated union: one object variant per status, each declaring only the fields that status owns. Narrowing on status then makes data available only in the success variant and error only in the error variant, so nullable-everywhere fields disappear.

open as a page

showing 1–30 of 49