React.Context reference
In a nutshell
Many React applications manage global-like data (e.g., locale preference, user authentication status, UI theme, etc.) that is used by several components at different layers of the component tree. Context lets you pass such data through the component tree without having to pass props down manually at every level of the tree.
Prop-drilling: the problem that context solves
Suppose you had an app with the following components (CodeSandbox):
const App = () => <Navbar user={{ name: 'John Doe' }} />
const Navbar = ({ user }) => (
<nav>
<UserDetails user={user} />
</nav>
)
const UserDetails = ({ user }) => <div>User: {user.name}</div>
Because UserDetails
requires the user
prop, you must pass down the user
prop through the intermediate Navbar
component. In this example, there’s only one intermediate component (Navbar
), but in many React apps, the component tree will have many layers so you must thread props through several layers.
This is a common problem and is often referred to as “Prop Drilling“. React Context exists to solve this exact problem.
How Context solves prop-drilling
Instead of passing down the user
prop through every intermediate component between App
and UserDetails
, you can create and use a UserContext
like this (CodeSandbox):
// Create a context for the current user (with a default value).
export const UserContext = React.createContext(null)
const App = () => (
// Use a Provider to pass the current user to the tree below.
// Any component can read it, no matter how deep it is.
// In this example, we're passing {user: "John Doe"} as the
// current value.
<UserContext.Provider value={{ name: 'John Doe' }}>
<Navbar />
</UserContext.Provider>
)
// Navbar doesn't have to pass the user down explicitly anymore.
const Navbar = () => (
<nav>
<UserDetails />
</nav>
)
const UserDetails = ({ user }) => {
const userContext = useContext(UserContext)
return <div>User: {userContext.name}</div>
}
Again, this example has only one intermediate component (Navbar
), but in most React apps, you will have many more intermediate components. The Context API helps you easily share global-like data with several components regardless of where they are in the component tree. All you have to do is:
- Create a context with an initial value using
React.createContext
. - Wrap the part of your component tree that you want to have access to the context with
<Context.Provider value={someInitialValue}>
. - Consume the context anywhere using
useContext
orMyClass.contextType
.
How context updates trigger re-renders
When the nearest <MyContext.Provider>
above the consuming component updates, the component will re-render with the latest value
prop of the nearest <MyContext.Provider>
.
Even if an ancestor component uses React.memo
or shouldComponentUpdate
, the component consuming the context will still re-render.
Subscribe to context in a class component
Once you’ve created a context using React.createContext
, you can assign that context to the contextType
class property on your component. You can then use this.context
to consume the nearest current value of the context.
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;
You can also use this alternative syntax:
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* render something based on the value */
}
}
How to implement a dynamic context
What if you need to be able to update a context? For example, suppose your app supports a light theme by default but lets users switch to a dark theme.
To implement dynamic context, you’d do something like the following (CodeSandbox):
1. Create context that uses state internally
import React, { useState } from 'react'
import Navbar from './Navbar'
const DEFAULT_THEME = 'dark'
// Create a context for the current user (with a default value).
export const ThemeContext = React.createContext({
theme: DEFAULT_THEME,
toggleTheme: () => {}
})
const App = () => {
const [theme, setTheme] = useState(DEFAULT_THEME)
const toggleTheme = () => {
setTheme((prevTheme) => (prevTheme === 'dark' ? 'light' : 'dark'))
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Navbar />
</ThemeContext.Provider>
)
}
export default App
2. Call the context method that updates the context’s internal state
import { useContext } from 'react'
import { ThemeContext } from './App'
// Navbar doesn't have to pass the user down explicitly anymore.
const Navbar = () => {
const { theme, toggleTheme } = useContext(ThemeContext)
return (
<nav>
<p>
Theme: <strong>{theme}</strong>
</p>
<button onClick={() => toggleTheme()}>Toggle theme</button>
</nav>
)
}
export default Navbar
Create custom hook to easily access context
Instead of:
const { theme, toggleTheme } = useContext(ThemeContext)
You can encapsulate a context behind a custom hook:
const { theme, toggleTheme } = useTheme()
useTheme
can be defined as something like this:
export function useTheme() {
const context = useContext(ThemeContext)
if (context === null) {
throw new Error(
'useTheme requires components to be wrapped in a ThemeProvider'
)
}
return context
}
Other notes
- Here’s a Sandbox demonstrating how to write a simple
AppSettings
context.
Sources
Thanks for your comment 🙏. Once it's approved, it will appear here.
Leave a comment