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?
answer
- object literal allocates every render
- Object.is, not deep equality
- useMemo on the value prop
- stable functions or the memo is pointless
- dispatch from useReducer is already stable
basics
~20 sThe 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.
solid answer
~40 sReact decides whether to notify consumers by comparing the old and new context value with `Object.is` — reference equality for objects. An inline `{{ user, login, logout }}` literal allocates a fresh object on every render of the surrounding component, so the comparison always fails and every consumer re-renders even though `user` is identical. The fix is to give the value a stable identity: `const value = useMemo(() => ({ user, login, logout }), [user, login, logout])`. That only helps if the functions are stable as well, so `login` and `logout` need `useCallback` or need to come from a `useReducer` `dispatch`, which is stable by construction. Memoizing the object while re-creating the functions inside it leaves you exactly where you started.
code
javascript · 14 linesimport { createContext, useCallback, useMemo, useState } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = useCallback((name) => setUser({ name }), []);
const logout = useCallback(() => setUser(null), []);
const value = useMemo(() => ({ user, login, logout }), [user, login, logout]);
return <AuthContext value={value}>{children}</AuthContext>;
}go deeper
Recognise that value={{ ... }} builds a brand-new object on every render, and that React treats a new object as a changed value.
Explain the Object.is comparison and write the fix end to end — useMemo on the value plus useCallback or a reducer dispatch for the functions inside it.
Show how you find this with the Profiler rather than memoizing on suspicion, and say when memoizing is unnecessary because the value is already stable.
Own the convention: decide whether providers in the codebase are required to hand out memoized values, and whether a lint rule or a shared provider factory should enforce it instead of relying on review.
## What React actually compares A context provider does not diff the contents of its value. It performs one comparison — `Object.is(previousValue, nextValue)` — and if that is false, it schedules an update on every component reading that context below it. `Object.is` is essentially reference equality for objects (it differs from `===` only for `NaN` and `-0`). Two objects with identical fields are still two objects. That single comparison is the whole story, and it is why this pitfall is so common: a context almost always carries several things at once — some state plus the functions to change it — and the natural way to bundle them is an object literal in JSX. An object literal is an allocation expression. It runs on every render. ```jsx function AuthProvider({ children }) { const [user, setUser] = useState(null); // new object identity on every render of AuthProvider return ( <AuthContext value={{ user, login: () => setUser(fetchMe()), logout: () => setUser(null) }}> {children} </AuthContext> ); } ``` If `AuthProvider` re-renders for any reason at all — an unrelated piece of its own state, a parent re-render — every consumer in the app is notified, even though `user` is the same object it was before. ## The fix, in two halves **Half one: memoize the container object.** ```jsx const value = useMemo(() => ({ user, login, logout }), [user, login, logout]); return <AuthContext value={value}>{children}</AuthContext>; ``` `useMemo` returns the same object across renders as long as every dependency is `Object.is`-equal to last time. Now the context value only changes when something inside it genuinely changed. **Half two: make the functions stable, or half one is theatre.** If `login` and `logout` are arrow functions defined in the component body, they are new function objects every render, so the dependency array changes every render, so `useMemo` recomputes every render, so the value identity changes every render. You have added a hook and fixed nothing. Wrap them: ```jsx const login = useCallback((creds) => setUser(authenticate(creds)), []); const logout = useCallback(() => setUser(null), []); ``` Or sidestep it: if the provider's state lives in `useReducer`, the `dispatch` function React hands you is stable across renders, so it needs no wrapping at all. The same trap applies to nested objects and to arrays: `useMemo(() => ({ config: { retries: 3 } }), [])` is fine because the whole thing is memoized, but `useMemo(() => ({ items: items.filter(Boolean) }), [items])` re-allocates whenever `items` changes identity — which may be every render if `items` itself is computed inline upstream. Stability is only as good as the least stable dependency. ## When you do not need any of this Memoizing is not free — it costs a hook, a dependency array to keep correct, and reader effort — so know when to skip it. - **The value is a single primitive.** `<ThemeContext value={theme}>` where `theme` is the string `"dark"` needs nothing: primitives compare by value. - **The value is the state object itself.** `<UserContext value={user}>` where `user` comes straight from `useState` is already stable between updates. - **The provider component only ever re-renders when the value genuinely changes.** If the only state in the provider is the state in the value, and nothing above it re-renders it, a fresh literal is fresh precisely when the data changed. This is fragile — one added `useState` in that component breaks it silently — so most teams memoize anyway as a defensive habit. ## The diagnostic In the React DevTools Profiler, record an interaction and look at what rendered. If a wide, unrelated set of consumers rendered together and the profiler attributes it to a context change, look at the provider's value expression first. Nine times out of ten it is a literal. A useful mental model for review: **treat the `value` prop of a provider as a memoization boundary.** Anything constructed inline there is being handed to potentially the entire application as "this changed". ## The related mistake Do not respond to this problem by wrapping the consumers in `React.memo`. A consumer re-renders because it reads a context whose value changed; `React.memo` compares props and cannot block that. The fix has to happen at the provider — stabilize the value, or split it so fewer components care about the part that changes.
- You wrap the value in useMemo but the consumers still re-render on every parent render. What is the most likely cause?A dependency in the array is itself unstable — usually an inline arrow function or an object built in the component body. `useMemo` returns the same object only while every dependency is `Object.is`-equal, so one fresh function reference per render makes it recompute every render. Stabilize the functions with `useCallback`, or use a reducer's `dispatch`.
- Is there a case where memoizing the provider value is unnecessary?Yes. If the value is a primitive, or is the state object straight out of `useState`, its identity is already stable between real updates. Memoizing then adds a hook and a dependency array for no benefit. Most teams still do it defensively, because adding any unrelated state to the provider component silently reintroduces the problem.
- The provider value is memoized correctly, but the whole subtree under the provider still re-renders whenever the provider component renders. Why?That is not the context mechanism — it is ordinary rendering. The provider component re-created its children's elements. Having the provider component accept and render a `children` prop, rather than constructing that JSX itself, keeps those elements identical across its renders.
saying these in an interview costs you the question
- Thinks React deep-compares the context value
- Memoizes the object but leaves inline arrow functions inside it
- Wraps consumers in React.memo to fix a provider-value problem
- Says useMemo guarantees a stable value regardless of dependencies
- Believes an unchanged user field means the value is unchanged