Why You Should Be Using React Context
Learn how to use the React Context API with the useContext Hook.
Introduction
As I started to get more comfortable with developing React applications, I stumbled across a frustrating problem in the applications I was building.
I wanted to be able to access information about the current signed-in user on multiple different pages and components.
To do that, I could either:
- Write the code to get the current user many times over
- Pass the user down as a
prop
to all the components that needed to use it, (and some parent props that didn't!)
I thought to myself, there must be a better way to do this, and I was right; enter the React Context API!
Video Lovers
For those who prefer to consume their content via video, I have also created a YouTube Guide to follow along with this blog :-) - Let me know if you come from Hashnode!
What is React Context
Context provides a way to share values between components, without having to explicitly pass a prop through every level of the tree. This avoids a problem known as prop drilling.
React Context is designed to share data that is considered "global"; like the current user, a UI theme, or local preferences; such as if the user selects dark mode or light mode.
Essentially, React Contexts allow you to store some value
within a parent component, and every child inside that parent can access the value
.
Why Do We Need React Context?
Let's look at an example of what life is like without React Context.
Let's say we have some logic to get the current user
in our parent component, Page
.
We also have a Link
component that I'd like to access the user
information in.
BUT the Link
component lives within the NavBar
component, and the NavBar
component lives within the PageLayout
component.
So our structure looks something like this:
-> Page
-> PageLayout
-> NavigationBar
-> Link
In order for the Link
to access the user
, we will have to pass it down the hierarchy as props, like so:
<Page user={user}>
<PageLayout user={user}>
<NavigationBar user={user} >
<Link href={user.permalink}/>
</NavigationBar>
</PageLayout >
</Page>
This is not only annoying to implement but the PageLayout
and NavigationBar
components may not even need the user
; they only need it to pass it down the chain to the Link
component.
Let's see how a React Context could help.
How To Create a React Context
The basic syntax of a React Context is:
// Create the context
const AuthContext = createContext({}); // {} here is the default value.
// A function that you would pass children components into.
// This function would return all the children passed into it, plus a context wrapped around it.
function AuthContextProvider({ children }) {
return (
<AuthContext.Provider value={/* Some value here*/}>
{children}
</AuthContext.Provider>;
}
value
can be any value of your choosing.
You can pass multiple values at once by passing in an object too :-)
How do I use the context?
Previously, (before React Hooks), you would have to use a Context.Consumer
to read the value stored in the Context.Provider
, which looked like this:
<AuthContext.Consumer>
{value => /* render something based on the context value */}
</AuthContext.Consumer>
But, since we live in the future™, we can use the more simple useContext Hook
The useContext
hook accepts the context object itself as an argument and returns the current context value, which is the current value of the value
prop of the context.
The code to initialize that hook looks like this:
const useUser = () => useContext(AuthContext)
In the real world, you would likely export this hook, and in any functional component that you want to get the value of the context, you can simply import it and use it, like so:
import useUser from '../context/userContext'
and then:
// myUser contains the value of the current value in the context.
const myUser = useUser()
No need for the Consumer
anymore!
How Does It Work?
Whenever the Context Provider
value updates, the useContext
hook triggers a re-render with the latest context value
that it has to all the subscribers of the context.
A component calling
useContext
will always re-render when the context value changes.
Real-World Examples
AWS Amplify Example
The example context I have hinted at in the above code snippets is to have the current signed-in user available within the context.
Let's explore how we can achieve that, with an example userContext
using AWS Amplify.
AuthContext.tsx
:
import React, { ReactElement, useState, useEffect } from 'react';
import { createContext, useContext } from 'react';
import { Auth, Hub } from 'aws-amplify';
// initialize the context with an empty object
const UserContext = createContext({});
export default function AuthContext({ children }) {
// Store the user in a state variable
const [user, setUser] = useState(null);
useEffect(() => {
checkUser();
}, []);
// (Only once) - when the component mounts, create a listener for any auth events.
useEffect(() => {
Hub.listen('auth', () => {
// Hub listens for auth events that happen.
// i.e. Sign in, sign out, sign up, etc.
// Whenever an event gets detected, run the checkUser function
checkUser();
});
}, []);
async function checkUser() {
try {
// Get the current authenticated user
const user = await Auth.currentAuthenticatedUser();
if (user) {
// set the user in state
setUser(user);
}
}
// Error occurs if there is no user signed in.
catch (error) {
// set the user to null if there is no user.
setUser(null);
}
}
return (
<UserContext.Provider
value={{
user, // the value of the current user
setUser, // the setState method - allows us to set the current user in state from anywhere in our application
}}
>
{children}
</UserContext.Provider>
);
}
// export the hook so we can use it in other components.
export const useUser = () => useContext(UserContext);
In my _app.js
file, (which in Next.JS, wraps all pages of the application):
import React, { useEffect } from 'react';
import AuthContext from '../context/AuthContext';
function MyApp({ Component, pageProps }) {
return (
<AuthContext>
<Component {...pageProps} />
</AuthContext>
);
}
export default MyApp;
Now, I can access the current signed-in user from any page in my application, with:
// De-structure user out of the value prop returned by useUser hook.
const { user } = useUser();
Conclusion
React Context and the useContext
hook is a powerful tool to manage a piece of data that:
- May change.
- Needs to be used by multiple components.
A helpful use case of React Context is to provide the current user to all the components of the application. I hope this helps you to understand what the React Context API is and how to use it in the modern age with hooks.
Thanks for reading :-)!
Connect With Me!
YouTube: youtube.com/channel/UCJae_agpt9S3qwWNED0KHcQ
Twitter: twitter.com/JarrodWattsDev
GitHub: github.com/jarrodwatts
LinkedIn: linkedin.com/in/jarrodwatts
Website: jarrodwatts.com