0% completed
Context API Use Cases and Best Practices
On This Page
Where Context Fits
Try Props and children First
Where Context Fits Poorly
A Value That Is New on Every Render
Practice 1: Separate Values That Change at Different Times
Practice 2: Keep Local State Local
Practice 3: Wrap Each Context in a Custom Hook
Fifty components that read the pointer position from one context rendered 500 times in total while the pointer moved ten times. Each movement gave the context a new value, and each new value re-rendered all fifty readers. Context is a good fit for some values and a poor fit for others, and the way a provider builds its value also decides how many components re-render. The sections below cover where context fits, where it does not, why a provider's value can re-render readers whose data did not change, and three practices that keep context code fast and easy to follow.
Where Context Fits
Context works best for a value that many components in different places read, and that changes far less often than it is read. Typical examples are:
- the color theme and other display settings
- the signed-in user and their permissions
- the interface language
- the state of one feature shared inside its own subtree, such as the cart from the previous lesson
Routing libraries use context in the same way. React Router, which you used in React Fundamentals, keeps the current location in a context, which is how every link can know whether it is active.
Try Props and children First
Prop drilling through two or three components is not a problem that needs context. Explicit props show exactly which component uses which data, and a reader of the code can follow them.
When a prop passes through many components that do not use it, the cause is often a layout component that renders its content itself. Let the layout accept its content as children instead:
// Before: Layout receives products only to pass them down <Layout products={products} /> // After: the parent renders ProductGrid itself, so Layout never sees products <Layout> <ProductGrid products={products} /> </Layout>
The data now goes from the parent straight to ProductGrid, with no intermediate layer. Use context when neither props nor children keep the code simple.
Where Context Fits Poorly
Values that change many times a second. Every change of the value re-renders every component that reads it. This is the pointer example from the start of the lesson: we put the pointer position in a context, rendered 50 components that read it, and moved the pointer 10 times:
C 50 consumers, 10 pointer moves: {"consumer":500}
That is 500 component renders for 10 small movements. Keep fast-changing values in local state, close to the one component that shows them.
Data from a server. Context can deliver fetched data, but it does nothing about loading states, caching, refetching, or stale responses. Chapter 5 introduces React Query, which handles those concerns.
Large app-wide state with many updates. When many unrelated parts of an app read and update one large store, a state library such as Redux lets each component subscribe to only the part it reads, and it records each action for debugging. Chapter 7 covers Redux.
| Need | Context | Redux (chapter 7) | React Query (chapter 5) |
|---|---|---|---|
| Theme, user, language | Good fit | More than needed | Not for this |
| A feature's shared client state | Good fit with useReducer | Good fit when large | Not for this |
| Data fetched from a server | Delivers it, no caching | Possible, with extra code | Good fit |
| A value that changes many times a second | Poor: every reader re-renders | Components can subscribe to a slice | Not for this |
| Extra library | None | Yes | Yes |
A Value That Is New on Every Render
The theme toggle lesson gave its provider an object, { theme, toggleTheme }, built inside App. Every render of App runs that line again and creates a new object and a new function, even when the theme has not changed. React compares the old and the new value with Object.is, which compares objects by identity, not by their contents. Two different objects are never equal, so React treats the context as changed and re-renders every component that reads it.
In the small apps of this chapter, you cannot see those extra renders, because the readers render again anyway when their parent App re-renders. They become visible, and worth removing, once the components between the provider and its readers are made to skip rendering, which chapter 2 teaches. The chapter 2 lesson "Memoization Using the useCallback Hook" measures the extra renders with a search box placed next to the theme and shows how to keep the value the same between renders.
Practice 1: Separate Values That Change at Different Times
The cart in the previous lesson used two contexts, one for the items and one for dispatch. To see why, we rebuilt it with one context whose value is { items, dispatch } and clicked "Add Keyboard" once:
one context, after Add Keyboard: Cart (1) {"CartBadge":1,"AddButton Keyboard":1,"AddButton Mouse":1}
Both AddButton components re-rendered, although they only read dispatch, which never changes. A component that reads a context re-renders when any part of the value changes, because React compares the whole value. With two contexts, the same click re-rendered CartBadge and CartList and left both buttons alone, as the previous lesson's run showed.
The same reasoning applies to unrelated data. If the theme and the signed-in user shared one context, a theme change would re-render every component that only shows the user's name. Give each concern its own context: ThemeContext, UserContext, CartContext.
Practice 2: Keep Local State Local
State that one component, or a parent and one child, uses does not belong in context. A text field's current input, whether a dropdown is open, or the current page of one table should stay in useState in the component that owns it. Putting such state in a shared context makes every reader of that context re-render on each change, and it makes the data flow harder to follow.
Practice 3: Wrap Each Context in a Custom Hook
The cart exported useCart and useCartDispatch instead of the context objects. Each hook checks that a provider exists and throws a clear error otherwise:
outside provider throws: useCart must be used inside a CartProvider
Callers then import one hook and never handle the context object directly. If you later change how the cart is stored, only the hooks and the provider need to change.
Reading Progress
0%
On This Page
Where Context Fits
Try Props and children First
Where Context Fits Poorly
A Value That Is New on Every Render
Practice 1: Separate Values That Change at Different Times
Practice 2: Keep Local State Local
Practice 3: Wrap Each Context in a Custom Hook