React useContext
useContext lets any nested component read a value from a shared context without threading it through every intermediate component's props.
The Prop-Drilling Problem
In a normal component tree, data flows down through props. If a deeply nested component needs a value owned by a distant ancestor, every component in between has to accept and forward that prop, even if it never uses it itself. This pattern, called prop drilling, gets unwieldy as trees grow.
Creating and Providing Context
createContext(defaultValue) creates a context object with a Provider component attached. Wrapping part of your tree in <SomeContext.Provider value={...}> makes that value available to every descendant, no matter how deeply nested, via the useContext hook — with no props passed along the way.
- Create a context object once with createContext, usually in its own module
- Wrap the relevant part of the tree in that context's Provider and pass it a value
- Call useContext(SomeContext) in any descendant that needs the value
- Every consuming component automatically re-renders when the Provider's value changes
Creating and providing a theme context
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext("light");
function Toolbar() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div className={`toolbar toolbar-${theme}`}>
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "dark" : "light"}
</button>
</div>
);
}
function App() {
const [theme, setTheme] = useState("light");
function toggleTheme() {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
export default App;
export { ThemeContext };
Any descendant, however deeply nested, reads the current value with useContext(SomeContext) — no props required along the way. When the Provider's value prop changes, every component that consumes it re-renders with the new value.
Consuming the theme context
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext("light");
function Toolbar() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<div className={`toolbar toolbar-${theme}`}>
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "dark" : "light"}
</button>
</div>
);
}
function App() {
const [theme, setTheme] = useState("light");
function toggleTheme() {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
export default App;
Auth context with a custom hook
import { createContext, useContext, useState } from "react";
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
function login(name) {
setUser({ name });
}
function logout() {
setUser(null);
}
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
return useContext(AuthContext);
}
function ProfileButton() {
const { user, login, logout } = useAuth();
if (!user) {
return <button onClick={() => login("Ada")}>Log in</button>;
}
return (
<div>
<span>Welcome, {user.name}</span>
<button onClick={logout}>Log out</button>
</div>
);
}
function App() {
return (
<AuthProvider>
<ProfileButton />
</AuthProvider>
);
}
export default App;
export { AuthProvider, ProfileButton };
Exercise: React useContext
What core problem does React Context solve?