Suggest an editImprove this articleRefine the answer for “Context optimization”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)Context re-renders **every** subscriber whenever the Provider's `value` reference changes, so optimizing it means splitting the context into meaningful parts, stabilizing `value` with `useMemo`/`useCallback`, and moving to selective subscriptions (selectors) or an external store for frequent updates. **Key point:** rare updates with simple data fit Context well; frequent, fine-grained updates are better moved to a store with selectors (`useSyncExternalStore`, Zustand, and so on).Shown above the full answer for quick recall.Answer (EN)Image## 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.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.