Skip to main content

Context optimization

1) Split the context into meaningful parts

Do not put everything into one "fat" value.

  • Before: AppContext = { user, theme, notifications, cart }
  • After: AuthContext, ThemeContext, UIContext, CartContext

This way, changes in the cart do not trigger re-renders in consumers of the theme, and so on.


2) Stabilize the Provider's value

A re-render of every subscriber fires whenever the reference changes. Give it stable references:

javascript
function AuthProvider({ children }) { const [user, setUser] = useState<User | null>(null); // stabilize the functions too const login = useCallback((u: User) => setUser(u), []); const logout = useCallback(() => setUser(null), []); // value only changes when user changes const value = useMemo(() => ({ user, login, logout }), [user]); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }

Avoid:

javascript
<AuthContext.Provider value={{ user, login: (u)=>setUser(u) }}>

(every re-render creates a new object/functions -> every subscriber updates).


3) Split "state" and "dispatch" into two contexts

dispatch/actions have a stable reference, so they can be handed out through a separate provider:

javascript
const CounterStateContext = createContext<number>(0); const CounterDispatchContext = createContext<React.Dispatch<Action>>(() => {}); function CounterProvider({ children }) { const [count, dispatch] = useReducer(reducer, 0); return ( <CounterDispatchContext.Provider value={dispatch}> <CounterStateContext.Provider value={count}> {children} </CounterStateContext.Provider> </CounterDispatchContext.Provider> ); } // A subscriber that only needs to dispatch actions does not re-render when count changes

4) Place the Provider as low in the tree as possible

The fewer subscribers inside the Provider's scope, the smaller the scale of updates.

  • Wrap only the branches that need it, not the whole <App />.

5) Use selectors (pinpoint subscriptions)

The use-context-selector library lets you subscribe to part of value, not the whole object.

javascript
import { createContext, useContextSelector } from 'use-context-selector'; const UserContext = createContext<{user: User, setUser:Fn}>(/* ... */); function UserName() { const name = useContextSelector(UserContext, v => v.user.name); return <span>{name}</span>; // Re-renders only if the name changed }

This gives granular updates, similar to Redux.


6) Selector wrappers + React.memo

Sometimes it is convenient to make a small selector component that reads the context and hands down exactly the needed prop to a memoized component.

javascript
const PriceView = React.memo(function PriceView({ price }: { price: number }) { return <span>{price}</span>; }); function PriceFromContext() { const price = useContext(CartContext).totalPrice; // only one field return <PriceView price={price} />; }

React.memo "cuts off" unnecessary re-renders below it here if the prop did not change.


7) Do not store "fat" / frequently changing structures in context

  • Put identifiers and simple data there.
  • Keep heavy collections/caches in an external store (useSyncExternalStore, Zustand/Jotai/Redux) or locally where they are used.

8) For frequent updates, prefer an external store

Context is not meant for high-frequency updates (timers, cursor position, input). For those:

  • Zustand/Jotai/Redux + selectors
  • useSyncExternalStore with your own subscription bus They update only the consumers that actually read the piece that changed.

9) Small habits that help a lot

  • Do not pass new arrays/objects into value without useMemo.
  • Actions/callbacks go through useCallback.
  • Do not compute something heavy on the fly inside Provider - memoize it or move it out.
  • In TypeScript you can type contexts separately: State and Actions - less temptation to "glue everything into one".

A short "cheat sheet" for choosing

  • Rare updates + simple data -> Context (with points 1-6 above).
  • Many different fields that change often -> split the contexts or move to a store with selectors.
  • Very frequent updates (input, cursor, window size) -> an external store/subscription (useSyncExternalStore, Zustand, and so on).

If you want, I can sketch a mini-refactor: send me your current Provider/consumers and I will convert it into a version with split contexts and selectors.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.