Lightweight version of Redux 2: useContext
React’s built-in Context API useContext provides a solution for managing global state across components.
This is particularly useful when you want to avoid prop drilling, which is when you have to pass props through many components that do not need them.
Let’s consider a simple application that manages the state of a shopping cart.
A Simple Shopping Cart Application
import { useReducer } from 'react'
const cartReducer = (state, action) => {
switch (action.type) {
case "ADD_ITEM":
return state + 1
case "CLEAR_CART":
return 0
default:
return state
}
}
const App = () => {
const [cart, cartDispatch] = useReducer(cartReducer, 0)
return (
<div>
<Display cart={cart}/>
<div>
<Button dispatch={cartDispatch} type='ADD_ITEM' label='Add Item' />
<Button dispatch={cartDispatch} type='CLEAR_CART' label='Clear Cart' />
</div>
</div>
)
}
This solution works, but it’s not optimal if the component structure gets complicated.
For example, the dispatcher should be forwarded using props through many components to the components that need it, even though the components in between in the component tree do not need the dispatcher.
Creating a Context with createContext
React’s built-in Context API provides a solution for us.
React’s context is a kind of global state of the application, to which it is possible to give direct access to any component app.
Let’s create a context in the application that stores the state management of the cart. The context is created with React’s hook createContext. Let’s create a context in the file CartContext.jsx:
import { createContext } from 'react'
const CartContext = createContext()
export default CartContext
The App component can now provide a context to its child components as follows:
import CartContext from './CartContext'
const App = () => {
const [cart, cartDispatch] = useReducer(cartReducer, 0)
return (
<CartContext.Provider value={[cart, cartDispatch]}>
<Display />
<div>
<Button type='ADD_ITEM' label='Add Item' />
<Button type='CLEAR_CART' label='Clear Cart' />
</div>
</CartContext.Provider>
)
}
As you can see, providing the context is done by wrapping the child components inside the CartContext.Provider component and setting a suitable value for the context.
The context value is now set to be an array containing the value of the cart, and the dispatch function.
Accessing the Context with useContext
Other components now access the context using the useContext hook:
import { useContext } from 'react'
import CartContext from './CartContext'
const Display = () => {
const [cart] = useContext(CartContext)
return <div>
{`Items in cart: ${cart}`}
</div>
}
const Button = ({ type, label }) => {
const [cart, dispatch] = useContext(CartContext)
return (
<button onClick={() => dispatch({ type })}>
{label}
</button>
)
}
In conclusion, React’s Context API provides a powerful mechanism for managing global state across components, providing a clean and efficient alternative to prop drilling.