Hi folks,
Managing data in an app is little tricky when many components are sharing and updating it. useState, useReducer, useMemo etc. are some state management hooks in React, which are very efficient and have their own ways to work in different scenarios. Though all these hooks are effective, there are still some situations where managing state becomes difficult.
If you already know why we need context hook, you can directly jump to its implementation here
Consider an app which works on user’s data. On loading, the app fetches this data from a backend and stores it in an app component. Later, this data is shared between many other children components. If child components are just using that data, then it’s fine. But, problems will arise, if one of them will update the data.
As we know, the app component fetches the data, we need to use prop drilling to share it with all the children. In this case, we create a prop userData
and pass it to all the children of this app component, making it look something like this -
This approach works when the children are just consuming the data and not updating it. But if you see in the diagram, the children four
is performing an update operation on our user data. After this update, the new version of data should be made available to all the other components.
If you notice, this transaction of data becomes fairly difficult when the app is complex and there are multiple states to handle across multiple components.
These are the scenarios where state management libraries like Redux are introduced in the app. But with React context in hand, we can do the state management efficiently and natively.
P.S Redux is a very good and very vast state management system. It is the best choice for complex applications. But if the app has only a few shared states, I prefer working with context over Redux.
What is Context?
React context is nothing but a global state to the app. It is a way to make a particular data available to all the components no matter how they are nested. Context helps you broadcast the data and changes happening to that data, to all the components. That’s why it is a very useful state management hook, when it comes to use cases like we discussed above.
You can read more on React context in the official documentation of react
How to use it?
Now that we understand the what and why behind a context. Let’s understand how we can use it. To create a context in any React app, you need to follow 4 simple steps -
1- Create a context
2- Create a provider
3- Add provider to the app
4- UseContext
These terms can become super confusing in the start. The best way to understand context is, consider it as a simple state, a state which we create using useState. The only thing context will do is to share this state and its changes throughout the app.
Hence, when we say, we are creating a context, we are creating a state! When we say we are creating a provider, as its name says, we are creating a wrapper component to provide that state to all the components. It is that simple!
Now, let's dive into code and create a context! In the below code, we will be covering step 1 and 2.
// UserDetailsProvider.js
import { createContext, useState } from 'react';
//create a context, with createContext api
export const userDetailsContext = createContext();
const UserDetailsProvider = (props) => {
// this state will be shared with all components
const [userDetails, setUserDetails] = useState();
return (
// this is the provider providing state
<userDetailsContext.Provider value={[userDetails, setUserDetails]}>
{props.children}
</userDetailsContext.Provider>
);
};
export default UserDetailsProvider;
In the code above, we have used createContext api to create our userDetailsContext
. Now, the context got created, so we will need to create a provider.
In the function UserDetailsProvider, we created a provider for userDetailsContext
. <contextname.Provider>
is a common syntax for creating it. Please note a value
prop here. The value prop will be used always to pass the shared state down. In this case, we are passing both state and setState functions down. This is because, even though any component updates the state, the global state can get updated which will be available for all the components.
Now that our context and provider are created. Let’s add the provider to the app. This is the most important step, as it will make the provider available to all components. Hence, let’s wrap our app component inside this provider. Our app component will look something like this -
//App Component
import { BrowserRouter, Switch, Route } from 'react-router-dom';
import { RouteWithSubRoutes } from './utils/shared';
import UserDetailsProvider from './context/UserDetailsProvider';
import routes from './Routes';
function App() {
return (
<BrowserRouter>
<Switch>
// As login do not require the userDetails state, keeping it outside.
<Route path='/' component={Login} exact />
// All other routes are inside provider
<UserDetailsProvider>
{routes.map((route) => (
<RouteWithSubRoutes key={route.key} {...route} />
))}
</UserDetailsProvider>
</Switch>
</BrowserRouter>
);
}
export default App;
In this code, the data will not be fetched by the app component. Note, here we are adding only those components inside UserDetailsProvider which actually needs this state.
So here we come to the last part, using this context in any component. You must have guessed, this step needs the hook useContext as we will be using a context here! (No claps on guessing 😛)
This is done in the same way as we declare a state using useState. Something like this -
// Profile.js
import { useEffect, useState, useContext } from 'react';
import { getUser } from '../../utils/api';
import { userDetailsContext } from '../../context/UserDetailsProvider';
const Profile = ({ email }) => {
// This is how we useContext!! Similar to useState
const [userDetails, setUserDetails] = useContext(userDetailsContext);
const [loading, setLoading] = useState(false);
const handleGetUser = async () => {
try {
setLoading(true);
let response = await getUser(email);
setUserDetails(response.data);
} catch (error) {
console.log(error);
// TODO: better error handling
}
setLoading(false);
};
useEffect(() => {
if (!userDetails) {
handleGetUser();
}
}, []);
return <div className='bg-gray-gray1 h-full'>// do something</div>;
};
export default Profile;
If you have noticed, the useContext looks similar to useState. And later we will be using it same as useState!! Hence, whenever the setUserDetails function is called, the state change will be effective throughout the app, saving too much prop drilling.
So, that’s all about useContext hook. I have also seen many examples of context api being used in toggling and setting themes for an app. Do share your use-cases for using this context api.
Thank you so much for reading this article and please let me know your comments/feedback/suggestions. You can also connect with me on Twitter or buy me a coffee if you like my articles.
Keep learning 🙌
Top comments (33)
You get into the issue with re-render soon, believed me I try to useContext as you mention, then the nightmere visit me when I do think complex
does that mean we should avoid using useContext.
I think there some use case that a good fit for useContext, for example you have some data that readonly between multiple components.
I want to use it for sessionmanger to pass user object in body tag.
stackoverflow.com/questions/763610...
Can you help me out with this
agree. for complex projects with complex frontend UX it is difficult and in-efficient for context
Which alternate concept are you preferring / using nowadays?
Love this one github.com/atlassian/react-sweet-s...
This is literally the best stuff I saw here, gracias 🙌
Somehow, i just think that useContext might be a good idea when you have small or medium apps. But then, the code grows larger and hard to maintain i use redux for that.
That is absolutely true and I have mentioned that in the article as well. Context can never be an alternative to any state management library like Redux, but it is good option when the app is fairly simple.
To me redux(as state mgmt) and context(as dependency injection) are complementary, so they could be both used in a fairly complex app. Redux at high above and context at finer granular part of page.
Why not use Zustand it has a similar api to useContext ? useContext is not meant for state management since it can cause unnecessary re-renders?
I recommend to check this out. Zustand can be used like redux but it has much more simple API, which can highly reduce development time and improve dev productivity.
github.com/pmndrs/zustand
yup. this is the correct answer. context is meant to be a dependency injection solution and not for state management.
React Hooks like
useContext
are definitely preferred over a somewhat complex Redux for state management.I also liked this:
Thank you 🙌
1
Article has too many ! and !!
Ideally in such cases, you user should also feel same emotion :) but that's not true for this article.
Example for articles see javascript.info/object here the ! is used to as a surprise, etc.
2
Article can be take simpler examples.
Leaving all the surrounding code out, explaining only the core concept, It won't be more than 20 lines. Once that is done, you can do more lines of explanation of real world example.
Hope the feedback helps! Otherwise please ignore!! :D
+1 for writing article though :)
It was a great article, thanks for writing.
I'm a big fan of React Context, but it's not a state management solution, it's made for sharing data between children and dependency injection, and not state management. I suggest using jotai or zustand for state management instead.
Yes, as a team we also choose to use useContext for state management rather than redux.
1- Learning curve is very easy.
2- Easy to implement
3- Does not require installing additional library
But I definitely agree that, you should consider for what is the requirement of your app
Thank you for the article.
I love using context. It's so much easier than any state management lib I've used.
Nice! in my project, I using context instead of redux, and it's still good so far.
Simple explanation. Thank you.
It would be nice to further explain that, when using multiple sets of data in useContext, how the React is re-rendering based on the change(s) of these sets of data? e.g. Is it re-rendering everything under the provider umbrella? or is it just re-rendering the components using the changed state data? This is always very confusing to me.