How to Properly Use the Context API in React With Hooks

How to Properly Use the Context API in React With Hooks

Learn how to create shared state using the context API
Ferenc Almasi β€’ 2023 January 20 β€’ Read time 9 min read
Learn how you can use the context API in React with hooks to create a shared state across your components.
  • twitter
  • facebook
React

This lesson is a preview from our interactive course

There are a couple more hooks we need to cover as part of this course, and one of them was created based on an entire API. The context API lets us read a context that is shared between components.

It is often used when a prop is needed in many places. The context API lets us avoid passing props around to simplify our codebase. There could also be cases where data is needed globally, such as user data or translations. The context API is well-suited for this task.


How to Use the Context API

To use the context API, we need to use the useContext hook. However, to use the hook, we need to create a context first. To create a new context in React, we can use the createContext call:

Copied to clipboard! Playground
import { createContext } from 'react'

const UserContext = createContext({});
const UserProvider = UserContext.Provider;

export { UserContext, UserProvider };
context.js
Creating a new context using createContext

Create a new file called context.js in the src directory and add the above code. This file creates a new context with a default value of an empty object. Notice that the createContext call also adds a Provider to our variable. We need to export both to start using the useContext hook.

To expose the context to our application, we need to wrap it with our UserProvider. To make it globally available, use it at the top of the React tree. Change index.js according to the following to make the context available:

Copied to clipboard! Playground
import React from 'react'
import ReactDOM from 'react-dom/client'
import './styles.css'

import { UserProvider } from './context.js'
import App from './App'

const user = {
    name: 'John',
    learning: 'React'
};

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <UserProvider value={user}>
            <App />
        </UserProvider>
    </React.StrictMode>
);
main.js
Wrap App with the UserProvider

Now the context is available in our app, with the value of the user object. To use the context, run the following example to replace the contents of App.js:

Copied to clipboard! Playground
import React, { useContext } from 'react'
import { UserContext } from './context'

const App = () => {
    const user = useContext(UserContext);

    return (
        <div className="welcome">
            <h1>πŸ‘‹ Welcome {user.name}</h1>
            <span>πŸ“– Currently learning {user.learning}</span>
        </div>
    )
}

export default App;
App.js
How to use useContext Execute code

To use the useContext hook, we need to pass the context that we want to use, in our case, the UserContext that we have exported from context.js. Using the same syntax, we can access the context anywhere in any component, without having to pass props down the tree.

Default values

Note that the default value that we defined inside context.js is not used anywhere. Instead, the value prop of the Provider is used as the value for the context. This is because useContext always uses the closest provider above it. In case it is not found, it uses the default value.

Change the default value to the following, then remove UserProvider from index.js to verify that the default value is used instead.

Copied to clipboard! Playground
import { createContext } from 'react'

const defaultUser = {
    name: 'Guest',
    learning: 'Unknown'
};

const UserContext = createContext(defaultUser);
const UserProvider = UserContext.Provider;

export { UserContext, UserProvider };
context.js
Change the default value in the context

Keep in mind that the default value is only used if there is no matching provider. If we have the provider, but without a value prop, React will still try to use the provider. This means only the last example is valid:

Copied to clipboard! Playground
// ❌ UserProvider missing the value prop
<UserProvider>
    <App />
</UserProvider>

// ❌ UserProvider is still missing the value prop
<UserProvider user={user}>
    <App />
</UserProvider>

// βœ”οΈ Always use a value prop for a provider
<UserProvider value={user}>
    <App />
</UserProvider>
Providers must always have a value prop

If you are getting undefined for a context, even with a default value, you likely have a provider without a value.

This behavior ensures that React won't break, even if we are missing a provider accidentally. It also helps with testing, as we don't have to set up providers for test cases.


Using Multiple Context

Of course, it is also possible to use as many providers in React as necessary. For example, when it doesn't make sense to use the same context for two different purposes, we can create another one. Create a new context called MetaContext inside context.js and export both the context and its provider.

Copied to clipboard! Playground
import { createContext } from 'react'

const UserContext = createContext({});
const UserProvider = UserContext.Provider;

const MetaContext = createContext({});
const MetaProvider = MetaContext.Provider;

export {
    UserContext,
    UserProvider,
    MetaContext,
    MetaProvider
};
context.js
Add a new context to context.js

Make sure to export the provider from the MetaContext variable. To expose the context to our application, wrap App into the MetaProvider with the following value:

Copied to clipboard! Playground
import React from 'react'
import ReactDOM from 'react-dom/client'
import './styles.css'

import { UserProvider, MetaProvider } from './context.js'
import App from './App'

const user = {
    name: 'John',
    learning: 'React'
};

const meta = {
    site: 'Webtips',
    plan: 'PRO'
}

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <UserProvider value={user}>
            <MetaProvider value={meta}>
                <App />
            </MetaProvider>
        </UserProvider>
    </React.StrictMode>
);
index.js
Add MetaProvider to index.js

In this example, the order in which the providers are defined doesn't matter, as long as they wrap the App component. Now we have another context available that we can reference with a different useContext hook:

Copied to clipboard! Playground
import React, { useContext } from 'react'
import { UserContext, MetaContext } from './context'

const App = () => {
    const user = useContext(UserContext);
    const meta = useContext(MetaContext);

    return (
        <div className="welcome">
            <h1>πŸ‘‹ Welcome {user.name}</h1>
            <span>
                πŸ“– Currently learning {user.learning},
                on {meta.site} as {meta.plan}
            </span>
        </div>
    )
}

export default App;
App.js
Using multiple useContext Execute code

Whenever the providers are starting to pile up, it is a common practice to create a component whose only purpose is to collect providers in one place. This way, we can keep index.js clean.

Copied to clipboard! Playground
// Using a separate component for collecting providers in one place
const ApplicationProvider = ({ children }) => (
    <UserProvider value={user}>
        <MetaProvider value={meta}>
            {children}
        </MetaProvider>
    </UserProvider>
)

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <ApplicationProvider>
            <App />
        </ApplicationProvider>
    </React.StrictMode>
);
Outsourcing providers
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master Reactinfo Remove ads

Updating Context

Context can also change over time. For example, we might want to update our user object from various components. To achieve this, we need to combine our context with a useState hook. Change App.js to the following and remove the provider from index.js:

Copied to clipboard! Playground
import React, { useState, useContext } from 'react'
import { UserProvider, UserContext } from './context'

const defaultUser = {
    name: 'John',
    learning: 'React'
};

const UpdateUser = () => {
    const [user, setUser] = useContext(UserContext);

    return (
        <button onClick={() => setUser({ ...user, name: 'Jane' })}>
            Update user
        </button>
    )
}

const App = () => {
    const [user, setUser] = useState(defaultUser);

    return (
        <UserProvider value={[user, setUser]}>
            <div className="welcome">
                <h1>πŸ‘‹ Welcome {user.name}</h1>
                <span>πŸ“– Currently learning {user.learning}</span>
                <div>
                    <UpdateUser />
                </div>
            </div>
        </UserProvider>
    )
}

export default App;
App.js
Updating context using useState Execute code

Here, we are passing the defaultUser to a useState hook. Both the user state as well as the setUser updater function are passed as the value for the provider. This means we can use the useContext hook like it was the useState hook from the App component.

Click on the button to update the context from the UpdateUser component. This way, we can access and update the state from anywhere in our application using the same useContext hook.


Overriding Context

Last but not least, it is also possible to override the value of a context. For example, if we need to use a different value for a specific segment of the application, we can nest the same provider with different values:

Copied to clipboard! Playground
<UserProvider value={user}>
    <UserProfile />
    <UserProvider value={admin}>
        <AdminDashboard />
    <UserProvider />
</UserProvider>
Overriding context values
  • twitter
  • facebook
React
Did you find this page helpful?
πŸ“š More Webtips
Mentoring

Rocket Launch Your Career

Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies:

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.