Exploring the Context API in React: Simplifying State Management
Table of contents
In previous blog posts we have seen how to manage state in React using hooks like useState, useReducer and state monitoring hooks like useMemo and useCallBack if you want full understanding of those please visit my blog here, to understand better about state management in React, in this blog post we are going to see in-depth about context API in react.
Why Context Api?
React is a framework that is used to create single-page applications, using component-based architecture. The problem with this is when we divide the UI into components we need to share data from one component to another component based upon logic, to understand this better let us take an example of user data, we fetch the user data in the login page or component and store it in the component state, now we need that user data on other pages like profile page(or component) orders page, etc. for this we pass the user data as props to other components, sometimes we need to send it to components which do not need the user data, this is called prop drilling lets take a code example for better understanding.
Code:
import './App.css'
// Parent Component
const Parent = ({ name }) => {
return <Child name={name} />;
};
// Child Component
const Child = ({ name }) => {
return <Grandchild name={name} />;
};
// Grand Child Component
const Grandchild = ({ name }) => {
return <p>{name}</p>;
};
const App = () => {
const name = "John";
return (
<div>
<h1>Prop Drilling Example</h1>
<Parent name={name} />
</div>
);
};
export default App
In the above example, we have a simple component hierarchy with an App component as the top-level component with a variable name, a Parent component as the intermediate component, and a Child component as the intermediate component. Inside the Child component, there is a Grandchild component.
The name prop needs to be passed down from the App component to the Parent component, then to the Child component, and finally to the Grandchild component. This is an example of prop drilling, where the name prop needs to be passed through each component in the hierarchy, even though some components don't need the prop themselves.
Prop drilling can become more complex in larger component trees, with multiple levels of components, and when passing multiple props. It can lead to code clutter, decreased maintainability, and potential performance issues.
To solve the problem of prop drilling, you can use React Context or state management libraries like Redux to provide a global state that can be accessed by any component in the component tree, eliminating the need for prop drilling. But for this lets settle with just context.
Lets see how to solve the above problem using context:
Code:
import './App.css'
import {createContext, useContext} from 'react'
// Parent Component
const Parent = () => {
return <Child/>;
};
// Child Component
const Child = () => {
return <Grandchild/>;
};
// Grand Child Component
const Grandchild = () => {
const contextName= useContext(NameContext);
return <p>{contextName}</p>;
};
const NameContext = createContext("");
const App = () => {
const name = "John";
return (
<div>
<h1>Context Example</h1>
{/* Provide the name value to the context */}
<NameContext.Provider value = {name}>
<Parent/>
</NameContext.Provider>
</div>
);
};
export default App
To solve the above problem we created a NameContext using the createContext function provided by React. This creates a context object that consists of a Provider and a Consumer. The Provider component is responsible for providing the context value, and the Consumer component is used to consume the context value.
Inside the Grandchild component, we use the useContext hook to access the name value from the NameContext. This allows us to directly access the context value without prop drilling through intermediate components.
In the App component, we wrap the Parent component inside the NameContext.Provider component and provide the name value as the context value. This makes the name value accessible to all components nested inside the Provider component, including the Grandchild component.
Using React Context eliminates the need for prop drilling and allows components to access the shared context value directly, making the code cleaner, more maintainable, and more efficient.
The above code is in my GitHub repository named native-context inside React-State-Management you can access it from here.
Context API Best Practices:
For the sake of explanation i have have given the entire code in one file that is app.js but there are few best practices that we need to follow when we develop huge projects or work in a team with many developers. The best practices are:
- Use a separate file to define your Context: It's a good practice to define your context object in a separate file to keep your code organized and easy to maintain.
- Keep Context API limited to global state management only: It's best to use the Context API for managing state that needs to be accessed across multiple components in your application. Avoid using it for state that only needs to be accessed within a single component, as it can lead to unnecessary complexity and performance issues.
- Use context providers sparingly: While context providers can be a powerful tool for managing global state, it's generally a good idea to use them sparingly. Instead, consider using props to pass data down through your component tree whenever possible.
- Use default values: When creating a new context, it's a good idea to provide a default value that will be used if no provider is present. This can help prevent unexpected errors and make your code more robust. Note that, for the project we did above, we used an empty string as the default value for the context object.
Inorder for to understand the best practices even better I have made a multi-language selector app that displays the word in the selected language the code is in github you can access it from here.
The code is as follows in with files names mentioned
Store.jsx // to create the context and Provider
import React, { createContext, useState } from 'react';
export const LanguageContext = createContext({});
export const LanguageProvider = ({ children }) => {
const [language, setLanguage] = useState('en');
return (
<LanguageContext.Provider value={{ language, setLanguage }}>
{children}
</LanguageContext.Provider>
);
};
LanguageSelector.jsx //component where user selects language
import { useContext } from 'react';
import { LanguageContext } from './Store';
export const LanguageSelector = () => {
const { language, setLanguage } = useContext(LanguageContext);
const handleLanguageChange = (e) => {
const newLanguage = e.target.value;
setLanguage(newLanguage);
};
return (
<select value={language} onChange={handleLanguageChange}>
<option value="en">English</option>
<option value="fr">French</option>
<option value="es">Spanish</option>
</select>
);
};
Greeting.jsx // component where we convert input word to the selected language
import React, { useContext } from 'react';
import { LanguageContext } from './Store';
export const Greeting = () => {
const { language } = useContext(LanguageContext);
let greeting = 'Hello';
if (language === 'fr') {
greeting = 'Bonjour';
} else if (language === 'es') {
greeting = 'Hola';
}
return <h1>{greeting}!</h1>;
};
App.jsx
import { useState } from 'react'
import './App.css'
import {LanguageProvider} from './Store'
import {Greeting} from './Greeting'
import {LanguageSelector} from './LanguageSelector'
export function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<h1>Language Selector using Context</h1>
<LanguageProvider>
<LanguageSelector/>
<Greeting/>
</LanguageProvider>
</div>
</>
)
}
export default App
so that's it for this blog post I hope you understood context API better than before, if you liked this post please make sure to leave a like, if you want more updates subscribe to my newsletter and bookmark this blog