Many times, in react, state information is used by multiple components. Information in React is usually shared using props. We use it for this purpose as well.
To resolve such situations, we define that data/function at a common parent component and then move it down using props. We call it lifting the state.
The Context API is an alternative to this. A context defines a scope with some information. All the components placed in this scope can use that information directly. In my opinion, it is similar to namespaces in C++.
The Problem with Props
Although passing props is a perfectly valid solution, it often creates an issue. There might be situations where the common parent is far from the actual component. So, we will need to move the data very deep down the UI tree and make it unnecessarily verbose. This issue is called "Prop Drilling".
Let's take an example to understand this. Let's say we have a CRUD app related to Books. Following is the structure of this application.
<BookCreate />
- Creates entry for the book. <BookList />
- Shows all the Books <BookShow />
- Display component for each book. <BookEdit />
- Component to editing book information.
The books
array is a state variable that contains all the books, and createBook
, editBook
, and deleteBook
are functions that manipulate the state.
As we can see, all the components use the books
state. So, we declare in the App component. So are the functions that manipulate it.
Now, these functions need to be moved way down the component tree. We will require a lot of props, resulting in prop drilling.
We can avoid this issue using Contexts. Context lets a parent component provide data to the entire tree below it.
Implementing Context
To implement a Context, you need to do the following three steps:
Creating a context
Provide the context scope
Use the Context
Creating a Context object
We use the createContext()
method for this.
import {createContext} from "react";
const Context = createContext(defaultvalue);
This method returns a context
object.
Providing the Context
Now, we create a scope using this context
object. Here, we define the values to pass to the child components. The context
object has a provider component for this purpose.
<Context.Provider value={valueToShare}>
{children}
</Context.Provider>
This component has a value
prop through which we associate values with the scope. The value of this prop becomes available to all the children components of the Provider
component.
Using the context
object
The children components extract these values using the useContext()
hook.
import {useContext} from "react";
const value = useContext(Context);
This hook takes the context
object as an argument and returns the values that are associated with it.
Here's an example with a simple implementation using contexts.
//Context.jsx
import { createContext } from "react";
const Context = createContext(1);
export default Context;
//Container.jsx
import React, { useContext } from "react";
import Context from "./Context";
function Container() {
const value = useContext(Context);
return <div>{value}</div>;
}
export default Container;
//App.jsx
import React from "react";
import Context from "./Context";
import Container from "./Container";
function App() {
return (
<Context.Provider value={5}>
<Container />
</Context.Provider>
);
}
export default App;
This way, information is shared between components without using any prop for passing data.
Some Extra bits on contexts
We can create multiple scopes using the same context object. We can provide each of them with different context values that the components within them can access. Replace the App code with the following code in the above example to see it live.
function App() { return ( <div> <Context.Provider value={3}> <Container /> </Context.Provider> <Context.Provider value={7}> <Container /> </Context.Provider> </div> ); }
We can also define a scope within another scope of the same context object, and both will provide different values to their child components. Here, a child can access all the data values belonging to the nearest parent Provider. Replace the App code with the following code in the above example to see it live.
function App() {
return (
<div>
<Context.Provider value={3}>
<Container />
<Context.Provider value={7}>
<Container />
</Context.Provider>
</Context.Provider>
</div>
);
}
- If a non-child component tries to use it, it gets the default value passed during the
createContext(defaultValue)
call. In such a situation, React searches for a parent provider for it. But as it does not find one, it returns the default value as a fallback. Replace the App code with the following code in the above example to see it live.
function App() {
return (
<div>
<Context.Provider value={3}>
<Container />
</Context.Provider>
<Container />
</div>
);
}
Practical Use Case
The most common use of contexts is State Management. Prop drilling often happens due to the sharing of state information between components. What would be better is to create a context for the state and put the component tree associated with that state within that.
Let's see our book example again. We declared the books
state and associated methods in the App
component. We then moved them to the actual location via props.
Alternatively, we can define the books
state and the methods in a context and provide it to the App component. This way, the App and all its children components have access to the books
state directly.
Here's the code for it
Some Consideration
Contexts are great tools for passing data deeply in a UI tree without tending to prop drilling. They are simple to implement, making it very tempting to use them. Thus, we may end up overusing them. To avoid this, we must understand the association between the information and the components using it.
If the information is associated with components which are related and mutually dependent on one another, it might be better to use the prop system as it would make the data flow explicit between them. For example, let's say we have a Form component with multiple form controls. Different form controls may depend upon the states of one another and thus require the sharing of information. Here, using props would be better as it would depict how data flows within the Form component.
On the flip side, if the information is associated with mutually independent components, it is better to use contexts. An example of this could be login information. We can use this in the navbar to show the username. We can use this on the profile page to display user details. We can use it application-wide, irrespective of the relationship between the components.
That's all folks
Contexts are great for sharing information when it needs to be moved deeply in a component tree. It is simple to implement and helps in clubbing data in one place.
This article is my understanding of the context API. You may give your insights in the comments. I would appreciate your feedback.
And hey, if you want to connect beyond these pages, catch me on Twitter! My handle is @AnshumanMahato_.
With this, I would like to conclude this post. Until next time, stay curious and keep exploring! ๐Thank You for reading this far. ๐