How to Use useContext in React

What you’ll build or solve

You’ll share data across multiple components without drilling props through every level.

When this approach works best

useContext works best when you need to:

  • Share app-wide settings like theme, language, or feature flags across many screens.
  • Store session data like the current user or auth state for lots of components.
  • Coordinate UI state across siblings, like a sidebar open state used by the header and layout.

Skip useContext when only a parent and one child need the data. Props stay simpler. Also, avoid putting fast-changing state, like a text input value, into context for large trees, since each update can re-render many consumers.

Prerequisites

  • A React app set up
  • Comfort with React components and useState

Step-by-step instructions

1) Create a context object

Create a context in its own file so you can import it anywhere.

// src/contexts/ThemeContext.js
import {createContext }from"react";

exportconstThemeContext=createContext("light");

createContext returns an object you will use in two places: the Provider and useContext. The "light" value is the default when no provider exists above the component.


2) Provide a value with Provider

Wrap the part of your app that should have access to the value.

// src/App.jsx
import {useState }from"react";
import {ThemeContext }from"./contexts/ThemeContext";
importPagefrom"./Page";

exportdefaultfunctionApp() {
const [theme,setTheme]=useState("light");

return (
<ThemeContext.Providervalue={theme}>
<Page
onToggle={() =>
setTheme((t) => (t==="light"?"dark":"light"))
        }
/>
</ThemeContext.Provider>
  );
}

What to look for:

  • Any component that needs the context must render under <ThemeContext.Provider>.
  • If it renders outside, it will receive the default value from createContext(...).

3) Read the value with useContext

Call useContext inside your component to read the nearest provider’s value.

JavaScript

// src/Header.jsx
import {useContext }from"react";
import {ThemeContext }from"./contexts/ThemeContext";

exportdefaultfunctionHeader() {
consttheme=useContext(ThemeContext);

return (
<headerclassName={theme==="dark"?"dark":"light"}>
      Header
</header>
  );
}

What to look for:

  • Call useContext at the top level of your component during render.
  • Don’t call it inside event handlers, loops, or conditionals.

Examples you can copy

Example 1: Share a theme value

// ThemeContext.js
import {createContext }from"react";
exportconstThemeContext=createContext("light");

JavaScript

// App.jsx
import {useState }from"react";
import {ThemeContext }from"./ThemeContext";
importHeaderfrom"./Header";

exportdefaultfunctionApp() {
const [theme,setTheme]=useState("light");

return (
<ThemeContext.Providervalue={theme}>
<Header/>
<button
onClick={() =>
setTheme((t) => (t==="light"?"dark":"light"))
        }
>
        Toggle
</button>
</ThemeContext.Provider>
  );
}

JavaScript

// Header.jsx
import {useContext }from"react";
import {ThemeContext }from"./ThemeContext";

exportdefaultfunctionHeader() {
consttheme=useContext(ThemeContext);
return<h1>Theme: {theme}</h1>;
}

Example 2: Pass multiple values through one provider

JavaScript

// AuthContext.js
import {createContext }from"react";
exportconstAuthContext=createContext(null);

JavaScript

// App.jsx
import {useState }from"react";
import {AuthContext }from"./AuthContext";
importAccountMenufrom"./AccountMenu";

exportdefaultfunctionApp() {
const [user,setUser]=useState({ id:1, name:"Sam" });

constauth= {
    user,
    signOut() {
setUser(null);
    },
  };

return (
<AuthContext.Providervalue={auth}>
<AccountMenu/>
</AuthContext.Provider>
  );
}

JavaScript

// AccountMenu.jsx
import {useContext }from"react";
import {AuthContext }from"./AuthContext";

exportdefaultfunctionAccountMenu() {
constauth=useContext(AuthContext);

if (!auth?.user)return<ahref="/login">Sign in</a>;

return (
<div>
<span>{auth.user.name}</span>
<buttononClick={auth.signOut}>Sign out</button>
</div>
  );
}

Example 3: Feature flags for conditional UI

// FlagsContext.js
import {createContext }from"react";
exportconstFlagsContext=createContext({ newNav:false });

JavaScript

// App.jsx
import {FlagsContext }from"./FlagsContext";
importNavfrom"./Nav";

exportdefaultfunctionApp() {
constflags= { newNav:true };

return (
<FlagsContext.Providervalue={flags}>
<Nav/>
</FlagsContext.Provider>
  );
}

JavaScript

// Nav.jsx
import {useContext }from"react";
import {FlagsContext }from"./FlagsContext";
importNewNavfrom"./NewNav";
importOldNavfrom"./OldNav";

exportdefaultfunctionNav() {
constflags=useContext(FlagsContext);
returnflags.newNav?<NewNav/>:<OldNav/>;
}

Common mistakes and how to fix them

Mistake 1: Using useContext outside the matching Provider

What you might do:

JavaScript

import {useContext }from"react";
import {AuthContext }from"./AuthContext";

exportdefaultfunctionAccountMenu() {
constauth=useContext(AuthContext);
return<p>{auth.user.name}</p>;
}

Why it breaks:

Without a provider above, auth will be the default value, often null, so reading auth.user throws an error.

Fix: Wrap your component tree with the provider.

<AuthContext.Providervalue={{ user: { name:"Sam" } }}>
<AccountMenu/>
</AuthContext.Provider>

Mistake 2: Context value changes cause extra re-renders

What you might do:

<AuthContext.Providervalue={{ user, signOut }}>
<App/>
</AuthContext.Provider>

Why it breaks:

If you create a new object every render, consumers can re-render more often than you expect.

Fix: Stabilize the value when needed, for example, with useMemo, or split context when performance becomes a real issue.

import {useMemo }from"react";

constvalue=useMemo(
  () => ({ user, signOut }),
  [user,signOut]
);

<AuthContext.Providervalue={value}>
<App/>
</AuthContext.Provider>;

Troubleshooting

  • If you see “Cannot read properties of null/undefined,” check provider placement and the context default. Make sure the provider wraps the component using useContext.
  • If you see “Invalid hook call,” confirm you only call useContext inside a React component or custom hook and that your React versions match.
  • If a consumer doesn’t update, check that you are updating the provider value. Updating an unrelated state won’t change the context.
  • If updates feel slow, check how many components consume the context. Context updates can re-render many consumers. Stabilize the value with useMemo or split contexts into smaller pieces when needed.

Quick recap

  • Create a context with createContext(defaultValue).
  • Wrap the tree with <YourContext.Provider value={...}>.
  • Read the value with useContext(YourContext) inside components.
  • Pass a single value or an object when you need multiple values.
  • If performance becomes an issue, stabilize provider values or split contexts.