React Context API allows you to easily access data at different levels of the component tree, without having to pass data down through props.
It uses Context.Provider
and Context.Consumer
Components to pass down the data but it is very cumbersome to write the long functional code to use this Context API.
Also Read: React Context for Beginners – The Complete Guide
So useContext
hook helps to make the code more readable, less verbose and removes the need to introduce Consumer Component.
In this post we will look at how to use useContext
.
Using Contexts Before the Introduction of Hooks
To use context before the release of hooks, we usually implemented it by following these 3 steps:
Step 1: We will create a Context, using React’s createContext()
method.
Step 2: We will use the Provider
in the high-level component to provide the Context Value.
Step 3: We will then Consume the Context value using render props pattern
Let’s dive into an actual project to understand more about Context API before the introduction of useContext
hook.
Step 1: Creating the Context
First, you need to create a context object using the createContext(default)
function from the ‘react’ library.
This context object will hold the data that you want to share across your application. Also it accepts one optional argument: the default value.
Create a new file named Context.jsx
in the src
folder and add the following code to create a context object:
Context.jsx
import { createContext } from 'react'; export const Context = createContext('Default Value');
Step 2: Providing the Context
Context.Provider
component available on the context instance is used to provide the context to its child components, no matter how deep they are.
To set the value of context use the value
prop available on the <Context.Provider value={value}/>
App.jsx
import { Context } from './Context'; import MyComponent from './MyComponent'; function App() { const value = 'My Context Value'; return ( <> <Context.Provider value={value}> <MyComponent /> </Context.Provider> </> ); } export default App;
Again, what’s important here is that all the components that would like later to consume the context have to be wrapped inside the provider component.
If you want to change the context value, simply update the value prop.
Step 3: Consuming the Context (Old way – render props pattern)
Consuming the context can be performed in 2 ways.
The first way (old way) by using a render function supplied as a child to Context.Consumer
special component available on the context instance:
MyComponent.jsx
import { Context } from './Context'; const MyComponent = () => { return <Context.Consumer>{(value) => <span>{value}</span>}</Context.Consumer>; }; export default MyComponent;
In case the context value changes, <Context.Consumer>
will re-call its render function.
Output:
Step 3: Consuming the Context (New way – by useContext Hook)
The second way is to use the useContext(Context)
React hook:
MyComponent.jsx
import React from 'react'; import { useContext } from 'react'; import { Context } from './Context'; const MyComponent = () => { const value = useContext(Context); return ( <> <span>{value}</span> </> ); }; export default MyComponent;
Output:
You can have as many consumers as you want for a single context. If the context value changes (by changing the value prop of the provider <Context.Provider value={value} />
), then all consumers are immediately notified and re-rendered.
If the consumer isn’t wrapped inside the provider, but still tries to access the context value (using useContext(Context)
or <Context.Consumer/>
), then the value of the context would be the default value argument supplied to createContext(defaultValue)
function that had created the context.
When Do You Need Context?
The main idea of using the context is to allow your components to access global data and re-render when that global data is changed.
Context solves the props drilling problem: when you have to pass down props from parents to children.
You can hold inside the context:
- global state
- theme
- application configuration
- authenticated user name
- user settings
- preferred language
- a collection of services
Use Case: Global User name
The simplest way to pass data from a parent to a child component is when the parent assigns props to its child component:
App.jsx
import UserInfo from './UserInfo'; function App() { const userName = 'Sunil Pradhan'; return ( <> <UserInfo userName={userName} /> </> ); } export default App;
UserInfo.jsx
import React from 'react' const UserInfo = ({userName}) => { return ( <div><span>{userName}</span></div> ) } export default UserInfo
The parent component <App/>
assigns userName
data to its child component <UserInfo name={userName}/>
using the userName
prop.
That’s the usual way how data is passed using props. You can use this approach without problems.
The situation changes when <UserInfo/>
component isn’t a direct child of <App/>
, but is buried somewhere deeper.
For example, let’s say that <App/>
component (the one having the global data userName
) renders <Layout/>
component, which in turn renders <Header/>
component, which in turn finally renders <UserInfo/>
component (that’d like to access userName
).
Here’s how such a structuring would look:
App.jsx
import Layout from './Layout'; function App() { const userName = 'Sunil Pradhan'; return ( <> <Layout userName={userName}>Main content</Layout> </> ); } export default App;
Layout.jsx
import React from 'react'; import Header from './Header'; const Layout = ({ children, userName }) => { return ( <> <Header userName={userName} /> <main>{children}</main> </> ); }; export default Layout;
Header.jsx
import React from 'react'; import UserInfo from './UserInfo'; const Header = ({ userName }) => { return ( <> <header> <UserInfo userName={userName} /> </header> </> ); }; export default Header;
UserInfo.jsx
import React from 'react' const UserInfo = ({userName}) => { return ( <div><span>{userName}</span></div> ) } export default UserInfo
Output:
You can see the problem:
Because <UserInfo/>
component renders deep down in the tree, all the parent components (<Layout/>
and <Header/>
) have to pass the userName
prop.
This problem is also known as props drilling.
React context is a possible solution. Let’s see how to apply it in the next section.
Context to the rescue
As a quick reminder, applying the React context requires 3 actors: the context, the provider extracted from the context, and the consumer.
Here’s how the sample application would look when applying the context to it:
UserContext.jsx
import { createContext } from 'react'; export const UserContext = createContext('Unknown');
App.jsx
import Layout from './Layout'; import { UserContext } from './UserContext'; function App() { const userName = 'Sunil Pradhan'; return ( <> <UserContext.Provider value={userName}> <Layout>Main content</Layout> </UserContext.Provider> </> ); } export default App;
Layout.jsx
import React from 'react'; import Header from './Header'; const Layout = ({ children }) => { return ( <> <Header/> <main>{children}</main> </> ); }; export default Layout;
Header.jsx
import React from 'react'; import UserInfo from './UserInfo'; const Header = () => { return ( <> <header> <UserInfo /> </header> </> ); }; export default Header;
UserInfo.jsx
import React from 'react'; import { useContext } from 'react'; import { UserContext } from './UserContext'; const UserInfo = () => { const userName = useContext(UserContext); return ( <div> <span>{userName}</span> </div> ); }; export default UserInfo;
Output:
Let’s look into more detail at what has been done.
First, const UserContext = createContext('Unknown')
creates the context that’s going to hold the user name information.
Second, inside the <App/>
component, the application’s child components are wrapped inside the user context provider: <UserContext.Provider value={userName}>
.
Note that the value
prop of the provider component is important: this is how you set the value of the context.
Finally, <UserInfo/>
becomes the consumer of the context by using the built-in useContext(UserContext)
hook. The hook is called with the context as an argument and returns the user name value.
<Layout/>
and <Header/>
intermediate components don’t have to pass down the userName
prop. That is the great benefit of the context: it removes the burden of passing down data through the intermediate components.
When Context Changes
When the context value is changed by altering value
prop of the context provider (<Context.Provider value={value} />
), then all of its consumers are notified and re-rendered.
For example, if we change the user name from ‘Sunil Pradhan’ to ‘Anil Panda’, then <UserInfo />
consumer immediately re-renders to display the latest context value:
UserContext.jsx
import { createContext } from 'react'; export const UserContext = createContext('Unknown');
App.jsx
import { useEffect, useState } from 'react'; import Layout from './Layout'; import { UserContext } from './UserContext'; function App() { const [userName, setUserName] = useState('Sunil Pradhan'); useEffect(() => { setTimeout(() => { setUserName('Anil Panda'); }, 2000); }, []); return ( <> <UserContext.Provider value={userName}> <Layout>Main content</Layout> </UserContext.Provider> </> ); } export default App;
Layout.jsx
import React from 'react'; import Header from './Header'; const Layout = ({ children }) => { return ( <> <Header/> <main>{children}</main> </> ); }; export default Layout;
Header.jsx
import React from 'react'; import UserInfo from './UserInfo'; const Header = () => { return ( <> <header> <UserInfo /> </header> </> ); }; export default Header;
UserInfo.jsx
import React from 'react'; import { useContext } from 'react'; import { UserContext } from './UserContext'; const UserInfo = () => { const userName = useContext(UserContext); return ( <div> <span>{userName}</span> </div> ); }; export default UserInfo;
Output:
you will see ‘Sunil Pradhan’ (context value) displayed on the screen. After 2 seconds, the context value changes to ‘Anil Panda’, and correspondingly the screen is updated with the new value.
The demo shows that <UserInfo/>
component, the consumer that renders the context value on the screen, re-renders when the context value changes.
UserInfo.jsx
import React from 'react'; import { useContext } from 'react'; import { UserContext } from './UserContext'; const UserInfo = () => { const userName = useContext(UserContext); return ( <div> <span>{userName}</span> </div> ); }; export default UserInfo;
Updating the Context
The React Context API is stateless by default and doesn’t provide a dedicated method to update the context value from consumer components.
But this can be easily implemented by integrating a state management mechanism (like useState()
or useReducer()
hooks), and providing an update function right in the context next to the value itself.
Also Read: useState in React: A Complete Guide
In the following example, <App/>
component uses useState()
hook to manage the context value.
UserContext.jsx
import { createContext } from 'react'; export const UserContext = createContext({ userName: '', setUserName: () => {}, });
App.jsx
import { useMemo, useState } from 'react'; import { UserContext } from './UserContext'; import UserNameInput from './UserNameInput'; import UserInfo from './UserInfo'; function App() { const [userName, setUserName] = useState('Sunil Pradhan'); const value = useMemo(() => ({ userName, setUserName }), [userName]); return ( <> <UserContext.Provider value={value}> {useMemo( () => ( <div> <UserNameInput /> <UserInfo /> </div> ), [] )} </UserContext.Provider> </> ); } export default App;
UserNameInput.jsx
import React from 'react'; import { useContext } from 'react'; import { UserContext } from './UserContext'; const UserNameInput = () => { const { userName, setUserName } = useContext(UserContext); const changeHandler = (event) => setUserName(event.target.value); return ( <div> <input type="text" value={userName} onChange={changeHandler} /> </div> ); }; export default UserNameInput;
UserInfo.jsx
import React from 'react'; import { useContext } from 'react'; import { UserContext } from './UserContext'; const UserInfo = () => { const { userName } = useContext(UserContext); console.log('Render'); return ( <div> <span>{userName}</span> </div> ); }; export default UserInfo;
<UserNameInput/>
consumer reads the context value, from where userName
and setUserName
are extracted. The consumer then can update the context value by invoking the update function setUserName(newContextValue)
.
<UserInfo/>
is another consumer of the context. When <UserNameInput/>
updates the context, this component is updated too.
Note that <App/>
memoizes the context value. Memoization keeps the context value object unchanged as long as userName
remains the same, preventing re-rendering of consumers every time the <App/>
re-renders.
Otherwise, without memoization, const value = { userName, setUserName }
would create different object instances during re-rendering of <App/>
triggering re-rendering in context consumers.
Output:
Conclusion
React Context provides data to components no matter how deep they are in the components tree.
Using the context requires 3 steps: creating, providing, and consuming the context.
When integrating the context into your application, consider that it adds a good amount of complexity. Sometimes drilling the props through 2-3 levels in the hierarchy isn’t a big problem.
Add comment