5.1 React Context
Sometimes there is application state that you need to share with many unrelated components, often deeply nested in your component tree. This is often referred to as "global state".
We could pass this state from component to component as props, but that is both tedious and error prone. Passing state via props through one or more components that don't actually use them, but just pass them on to the next component, is called prop-drilling.
The React Context API provides another way to solve this.
Context lets us pass a value deep into the component tree without explicitly threading it through every component.
Read this first
The official React documentation warns that while the Context API is a good solution in many cases, there are alternatives that should be considered first. In particular, component composition.
# Create a Context object
There are two parts to the Context API: the provider, and the consumer. The provider is a special React component that hold any valid JavaScript data type and announces changes to that value. The consumers subscribe to updates from the provider and receive the value to use like props.
Creating a new context is simple. Suppose that you want to make the current logged-in user object available throughout your component tree.
const UserContext = React.createContext()
Notice the capitalization
Remember the Context object returned from React.createContext()
is a React component, and should be written with Pascal case.
The UserContext
object has a provider
property that holds a JSX component that you can use to make the context value available in the component tree.
function App () {
const [user, setUser] = useState()
return (
<div class='App'>
<UserContext.Provider value={user}>
<Toolbar />
<MainContent />
</UserContext.Provider>
</div>
)
}
# Use the Context value
Read this second
Consuming the Context provider is now simpler with the useContext() hook.
In any component that has the context provider as an ancestor component, you can now easily access the value
with the useContext()
hook.
function Toolbar () {
return (
<header>
{/* other stuff */}
<UserMenu />
</header>
)
}
function UserMenu () {
const user = useContext(UserContext)
return (
<div>
<span>{user.firstName}</span>
{/* other stuff */}
</div>
)
}
# Wrap it up in a custom hook module
It is often useful to encapsulate the context functionality in a custom hook module.
/** @module UserContext */
import React from 'react'
const UserContext = React.createContext()
function UserProvider (props) {
const [user, setUser] = React.useState()
function logout () {
setUser(null)
}
return <UserContext.Provider value={[user, logout]} {...props} />
}
function useUser () {
const context = React.useContext(UserContext)
if (!context) throw new Error('useUser must be used within a UserProvider')
return context
}
export { UserProvider, useUser }
/** @module App */
import { UserProvider } from './UserContext'
function App () {
return (
<div class='App'>
<UserProvider>
<Toolbar />
<MainContent />
</UserProvider>
</div>
)
}
/** @module UserMenu */
import { useUser } from './UserContext'
function UserMenu () {
const [user, logout] = useUser()
return (
<div>
<span>{user.firstName}</span>
<span onClick={logout}>Logout</span>
</div>
)
}