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 🙌