0% completed
How to Use the Context API and the useContext Hook
On This Page
Step 1: Create the Context in Its Own File
Step 2: Provide a Value That Comes from State
Step 3: Read the Value Where It Is Needed
What Happens When You Click
Passing a Function Through Context
Two Mistakes to Recognize
What does it take for a button in one corner of the page to change the colors of a component in another corner? With context, the answer is three small pieces: a context object, a provider whose value comes from state, and the components that read it. This lesson builds that theme toggle for the store page, one file at a time, and then follows a single click through React.
Step 1: Create the Context in Its Own File
// ThemeContext.js import { createContext } from 'react'; export const ThemeContext = createContext('light');
The context lives in its own module and is exported. Every component that provides or reads the theme imports this one object. If two files each called createContext, they would create two unrelated contexts, and a component reading one would never see the other's provider.
Step 2: Provide a Value That Comes from State
The theme must be able to change, so it lives in state. App owns that state and passes it to the provider as value:
// App.jsx import { useState } from 'react'; import { ThemeContext } from './ThemeContext'; import Page from './Page'; export default function App() { const [theme, setTheme] = useState('light'); function handleToggleClick() { setTheme((current) => (current === 'light' ? 'dark' : 'light')); } return ( <ThemeContext value={theme}> <button onClick={handleToggleClick}>Toggle theme</button> <Page /> </ThemeContext> ); }
The update uses the updater form, setTheme((current) => ...), so the new theme is always computed from the latest state. The toggle button is inside App, next to the state it changes.
Step 3: Read the Value Where It Is Needed
Two components read the theme. ThemeButton sits two levels down inside Toolbar, and ThemeLabel sits directly inside Page:
// Page.jsx import { useContext } from 'react'; import { ThemeContext } from './ThemeContext'; function ThemeButton() { const theme = useContext(ThemeContext); return <button className={theme}>Save ({theme})</button>; } function ThemeLabel() { const theme = useContext(ThemeContext); return <p>The theme is currently: {theme}</p>; } function Toolbar() { return ( <div> <ThemeButton /> </div> ); } export default function Page() { return ( <main> <Toolbar /> <ThemeLabel /> </main> ); }
Neither Page nor Toolbar receives a theme prop. In a real project, the className would select light or dark styles from a stylesheet.
What Happens When You Click
We rendered App, clicked "Toggle theme" once, and printed the screen text and the class on the Save button:
initial: The theme is currently: light | Save (light) after click: The theme is currently: dark | Save (dark) class= dark
The click starts a short sequence of events:
handleToggleClickcallssetTheme, and React schedules a re-render ofApp.Apprenders again withthemeequal to"dark"and gives the provider the new value.- React compares the provider's old value with the new one using
Object.is. The strings"light"and"dark"differ, so the context has changed. - React re-renders every component below the provider that reads
ThemeContext, hereThemeButtonandThemeLabel, and they return markup with"dark". - React updates the button text, its class, and the label in the DOM.
Follow the arrows from the click to the two readers. The comparison in step 3 is the point where React decides whether readers must update: if the new value is equal to the old one, the readers are not re-rendered because of the context.
In this small app, Page and Toolbar also render again, because their parent App re-rendered, and by default React re-renders the children of a component that re-renders. Context does not cause those two renders, and it does not prevent them either. Chapter 2 shows how to make React skip a child whose props have not changed.
Passing a Function Through Context
So far only App can change the theme. Suppose the toggle button should live inside Toolbar instead. The button then needs both the current theme and a way to change it, so the provider's value becomes an object that holds both:
// App.jsx export default function App() { const [theme, setTheme] = useState('light'); function toggleTheme() { setTheme((current) => (current === 'light' ? 'dark' : 'light')); } return ( <ThemeContext value={{ theme, toggleTheme }}> <Page /> </ThemeContext> ); }
// in Page.jsx function ThemeToggle() { const { theme, toggleTheme } = useContext(ThemeContext); return <button onClick={toggleTheme}>Switch from {theme}</button>; }
Every reader now destructures the object, so ThemeLabel reads const { theme } = useContext(ThemeContext). Change the default in createContext to match the new shape, for example createContext({ theme: 'light', toggleTheme: () => {} }), so that a reader outside any provider still gets an object.
This version works, and many codebases write it exactly this way. It has one cost: { theme, toggleTheme } creates a new object each time App renders, so Object.is reports a change on every render of App, even when the theme is the same. The last lesson of this chapter measures that cost and shows the fix.
Two Mistakes to Recognize
The provider must be above the reader, in a parent component. A useContext call does not see a provider that the same component returns. In this component, the paragraph shows the default value:
function SameComponent() { const theme = useContext(ThemeContext); return ( <ThemeContext value="dark"> <p>reader sees {theme}</p> </ThemeContext> ); }
reader sees light
Move the provider into a parent component, or move the reading code into a child component rendered inside the provider.
A component outside every provider silently gets the default. A ThemeLabel rendered outside App's provider printed The theme is currently: light with no warning. If you see the default value where you expected live state, check that the component is really rendered inside the provider.
Reading Progress
0%
On This Page
Step 1: Create the Context in Its Own File
Step 2: Provide a Value That Comes from State
Step 3: Read the Value Where It Is Needed
What Happens When You Click
Passing a Function Through Context
Two Mistakes to Recognize