WittCode💻

React Context Explained

By

Learn about react context including react providers, react consumers, react context re-rendering, and the react useContext hook.

Table of Contents 📖

What is React Context?

React context is a tool introduced in React version 16 that allows for easy sharing of state among components. In other words, React context allows us to pass data between components without using props.

What should be Stored in React Context?

React context is a way to manage state globally, but not all data should be stored in React context. Only data that does not change very often should be used with react context. For example, React context could contain information on the currently authenticated user or application preferences such as light or dark theme. This is because React context data is meant to be consumed not frequently updated.

Why was React Context Created?

React context was created to avoid prop drilling. Prop drilling is when props are passed down through multiple levels of components to reach its final destination. Usually when prop drilling happens, components that don't even need the props have it passed through them. Take the example below.

import React from "react"

function App() { const [user, setUser] = React.useState({name: 'WittCode', favoriteFood: 'Pizza'}); return }

function Component1({user}) { return }

function Component2({user}) { return }

function Pizza({user}) { return

{user.name}'s favorite food is {user.favoriteFood}

}

Here, we have a user object that we pass through Component1, to Component2, and finally to the Pizza component. Notice how Component1 and Component2 don't even touch the user object. This is a classic example of prop drilling. With React context, we could have the Pizza component consume the user data directly without passing it through Component1 and Component2.

Creating React Context

So, lets change our application here to use React context as opposed to prop drilling. Specifically, we will create a React context for our user object. That way we can have it consumable throughout the application. To begin, we first need to create a context. We can create a context using the createContext method. This method is native to the react library.

const UserContext = React.createContext();

The createContext method returns a context. We capture it in a variable called UserContext. UserContext is now a react context object that has two useful properties, Provider and Consumer.

React Context Provider

Every React context object comes with a Provider. A Provider is simply a React component. So, lets return the Provider component from within our App component.

function App() { const [user, setUser] = React.useState({name: 'WittCode', favoriteFood: 'Pizza'}); return ( <UserContext.Provider> </UserContext.Provider> ) }

Now, anything placed within this Provider component will be able to access our user context. However, we need to specify what our Provider component can provide. This can be done with the value prop. Whatever we pass to the value prop will be provided to any component nested within the Provider. For us, this is our user object. We also want Component1 and its children to be able to access the user object.

function App() { const [user, setUser] = React.useState({name: 'WittCode', favoriteFood: 'Pizza'}); return ( <UserContext.Provider value={user}> </UserContext.Provider> ) }

React Context Consumer

The other component that React context provides is the Consumer component. The Consumer component is simply a React component that subscribes to context changes. To demonstrate this, lets first remove the prop drilling that we have with our user object.

import React from "react"

const UserContext = React.createContext();

function App() { const [user, setUser] = React.useState({name: 'WittCode', favoriteFood: 'Pizza'}); return ( <UserContext.Provider value={user}> </UserContext.Provider> ) }

function Component1() { return }

function Component2() { return }

function Pizza() { }

Now, lets access our react context from within our Pizza component by using our Consumer component. The Consumer component requires a function as a child. This function receives the current value of the react context. In this instance, this is our user object. This function also returns a React node. So, within our Pizza component, lets access our user object with our Consumer component.

function Pizza() { return ( <UserContext.Consumer> {user =>

{user.name}'s favorite food is {user.favoriteFood}

} </UserContext.Consumer> ) }

So, the argument to this function is what we provided to the Provider component's value prop. This is the user object. Therefore, we can print the name and favorite food properties. This is a lot cleaner and avoids passing the user object through Component1 and Component2, both of which don't use the user object.

React useContext Hook

Using the Consumer component is fairly verbose and can cause a pyramid of doom the more contexts we consume. Luckily, React version 16.8 released the useContext hook. The useContext hook is a simpler way to consume data from a Provider. To use the useContext hook, import it from the React library. What it takes as an argument is the react context we want to consume from.

function Pizza() { const user = React.useContext(UserContext); return

{user.name}'s favorite food is {user.favoriteFood}!

}

Here, we use the useContext hook to consume the user data. We can then print the user's name and favorite food as we did before. It is clear that the useContext hook is a lot less verbose than the Consumer component. Another benefit of the useContext hook is we can create a custom hook from it. Nevertheless, both the Consumer component and useContext hook allow us to access the data from the Provider component.

React Context and Re-rendering

Previously, it was mentioned that React context should only contain data that is not frequently updated because React context is meant for data that is to be consumed, not updated. This is because if the context data updates, any component that consumes that context will re-render. Therefore, a larger application with multiple nested components that all consume a context could suffer from performance issues if that context is frequently updated.