Unlock the full potential of the React Context API! Discover this powerful tool and simplify your code and performance.
React JS is a widely used frontend web development framework written in Javascript. It was developed at Facebook and released in 2013 to all developers as an open source framework for building dynamic and reactive web UIs.
A reactive web UI is a web application view that automatically updates in response to any data or user input changes, without requiring a page refresh.
ReactJS uses a virtual DOM to efficiently update the UI when data changes, resulting in a fast and responsive user experience.
The example below is a small reactive application that updates the count label every time the increment button is clicked, without refreshing the page.
import React, { useState } from 'react'; function Counter { const (count, setCount) = useState(0); function increment { setCount(count + 1); } return ( < div > < p >Count: {count}</ p > < button onClick={increment}>Increment</ button > </ div > ); }
You can test the example above at
ReactJS and Virtual DOM components
What are React components?
A ReactJS component is a piece of Javascript code that can be used as an independent building block to build front-end web applications.
In ReactJS, you create reusable web components that can be rendered countless times on a web page and then reactively re-rendered on any data update or user input.
ReactJS updates these components without requiring a full page refresh, taking advantage of its virtual DOM concept.
What is a Virtual Gift?
Virtual DOM is a concept where a “virtual” representation of the user interface (HTML document) is stored in memory and kept synchronized with the “real” representation. DOM using a library like ReactDOM.
An example of a Reactjs component using props
In the example below, there is a simple ReactJS component that renders a button: –
import React from "react"; export default function Button(props) { return < button onClick={props.onClick}>{props.label}</ button >; }
The ReactJS component above is a JS function that takes a single argument called props and returns an HTML tag
To use this component in your ReactJS application, you can simply import it and render it like this: –
import React from 'react'; import Button from './Button';export default function App { function handleClick { console.log('Button clicked!'); } return ( < div > < Button onClick={handleClick} label="Click me!" /> <br /> < Button onClick={handleClick} label="Click me AGAIN!" /> </ div > ); }
I'm rendering the same component twice, but with different accessories (label).
You can test the example above at
In the screenshot above you can also see the Virtual DOM in the bottom right corner (highlighted in SkyBlue).
How to pass data from one Reactjs component to another?
In the above examples, we created our ReactJS applications with just a single component, be it the counter application or the click me application.
However, in a real-world ReactJS web application, we often have multiple components interacting with each other through a data passing methodology called 'support drilling'.
What are props?
Just like we can pass data to a JS function using a feature called 'arguments', similarly we can pass data to a ReactJS component using a feature called props which is shorthand for properties to do some operations on them and then return to us a reactive UI.
See the click me app example above, where we are passing the label text and onClick event data as props to our component
const (isLoggedIn, setIsLoggedIn) = React.useState(false); return ( < div > < Header isLoggedIn={isLoggedIn} /> < Main isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} /> < Footer /> </ div > ); } function Header({ isLoggedIn }) { return ( < header > < nav > {isLoggedIn ? < LogoutButton /> : < LoginButton />} </ nav > </ header > ); } function Main({ isLoggedIn, setIsLoggedIn }) { return ( <main> < Profile isLoggedIn={isLoggedIn} /> < Settings isLoggedIn={isLoggedIn} setIsLoggedIn={setIsLoggedIn} /> </ main > ); } function Profile({ isLoggedIn }) { return ( < div > < h2 >Profile</ h2 > {isLoggedIn ? < p >Welcome back!</ p > : < p >Please log in.</ p >} </ div > ); } function Settings({ isLoggedIn, setIsLoggedIn }) { return ( < div > < h2 >Settings</ h2 > {isLoggedIn ? ( < button onClick={ => setIsLoggedIn(false)}>Log out</ button > ) : ( < p >Please log in to change your settings.</ p > )} </ div > ); } function Footer { return < footer >© 2023 My App </footer> ; }In the example above we have a ReactJS application where const isLoggedIn data is passed to the Header and Main components, and also to the 3rd level Profile and Settings components through an intermediate Main component using props and prop drilling methodology .
What is a component tree?
A component tree in a ReactJS application is a representation of components organized in a hierarchy.
This hierarchical structure is built by nesting components within each other, with the top-level component being the root and child components located below it.
The image above represents a component tree of the vertical drilling example mentioned in the previous section.
Disadvantages of passing data using support drilling in a component tree
There are times when it is best to avoid propeller drilling as it has its disadvantages:
Data passing flow: When using props, you need to pass props at each level of a component tree.
Complexity : Drilling complexity can increase as your ReactJS web application grows and becomes more difficult to maintain and debug.
Boilerplate code : Prop drilling requires you to write more code during React app development to pass the same data through multiple component levels, leading to an increase in boilerplate code and a less efficient codebase for your ReactJS app .
Coupling : It can create tight coupling between components, which means changes to one component can affect other components, making your application's codebase more fragile.
Performance issues : Passing large or complex data through multiple levels of components using props can negatively impact the performance of your ReactJS application.
React Context API
To develop ReactJS web applications more efficiently, helping developers solve the above drawbacks, the ReactJS core team introduced in version 16.3 an API called Context API, with which you can pass data in a component tree without fixtures .
With this API in ReactJS, you can share data between components, in a tree of components, that are not directly related to each other, eliminating the need to manually pass props at each level.
What are the benefits of using the Context API over Prop Drilling?
Using React Context API instead of support drilling gives us benefits which are: –
Context API | Propeller Drilling | |
Data passing flow | Uses a supplier-consumer model to pass data through the component tree | Passes data through the component tree as accessories |
Complexity | Reduces complexity by eliminating the need for props to be passed through intermediate components | May result in increased complexity as components need to be aware of props they do not use and may have to pass them to child components |
Standard code | Reduces boilerplate code by providing centralized data storage | May result in increased boilerplate code as components need to pass fixtures to child components |
Coupling | Reduces coupling between components as components no longer depend on each other for data flow | May result in greater coupling as components need to be aware of the data they need to receive and may have to pass it on to child components |
Performance issues | Provides a faster and more efficient way to pass data as it eliminates the need to pass props through intermediate components | Can result in performance issues as props are passed through multiple levels, leading to longer render times and possible memory leaks |
Example
For example in our component tree, shown in the previous section, using the ReactJS Context API we can pass data directly from the App component to the Profile and Settings components without passing data through the Main component.
Let’s look at the same support drilling example rewritten using the ReactJS context API: –
import React, { createContext, useState } from 'react'; const AppContext = createContext ; function App { const (isLoggedIn, setIsLoggedIn) = useState(false); return ( < AppContext.Provider value= {{ isLoggedIn, setIsLoggedIn }} > < div > < Header /> < Main /> < Footer /> </ div > </ AppContext.Provider > ); } function Header { const { isLoggedIn } = useContext(AppContext); return ( < header > < nav > {isLoggedIn ? < LogoutButton /> : < LoginButton />} </ nav > </ header > ); } function Main { return ( < main > < Profile /> < Settings /> </ main > ); } function Profile { const { isLoggedIn } = useContext(AppContext); return ( < div > < h2 >Profile</ h2 > {isLoggedIn ? < p >Welcome back!</ p > : < p >Please log in.</ p >} </ div > ); } function Settings { const { isLoggedIn, setIsLoggedIn } = useContext(AppContext); return ( < div > < h2 >Settings</ h2 > {isLoggedIn ? ( < button onClick={ => setIsLoggedIn(false)}>Log out</ button > ) : ( < p >Please log in to change your settings.</ p > )} </ div > ); } function Footer { return < footer >© 2023 My App </footer> ; }
In the code above, I first created a context called AppContext using a createContext method provided by the React Context API.
I then provided this context by returning the AppContext.Provider component with the 'value' property, from my parent App component, to the child components I want to share my data with.
And finally, I consumed data, using the useContext(AppContext) hook, in my Header, Profile, and Settings child components.
As you can see, we have completely eliminated the need to pass data to the intermediate Main component, making our ReactJS code easier to maintain.
How to use the React Context API in your React application?
There are 5 steps required to use React Context API when developing your Reactjs application: –
1. Create your context object using the createContext method
You need to create a context, using the createContext method, which will act as a data store. Note:- You can create any number of contexts in your React application.
const AppContext = createContext ;
2. Provider and Consumer Components
The AppContext object we created using the createContext method above basically returns two components; the Provider to pass the data through the component tree using the 'value' property and the Consumer to read this passed data.
3. Wrap your component tree in the Provider component
Wrap your component tree with the Provider component. This must be done at the highest level of the component tree that needs access to context data.
return ( < AppContext.Provider value= {{ isLoggedIn, setIsLoggedIn }} > < div > < Header /> < Main /> < Footer /> </ div > </ AppContext.Provider > );
4. Pass context data to your Provider React component
You can add any value you want to the Provider component using the value proposition. This value can be a string, object or function and will be available to all consuming components.
< AppContext.Provider value= {{ isLoggedIn, setIsLoggedIn }} >
5. Read context data using the Consumer component
Read the context value into any component that needs it using the Consumer component. The Consumer component takes a function as a child and passes the current context value as an argument to that function. In our example above, however, we are using the useContext hook to get data.
const { isLoggedIn } = useContext(AppContext);
What is the value of context?
As mentioned above in step 4, the Provider component in the React Context API accepts one argument, the 'value' property, which is basically a global data store also called a context value.
This context value is then shared among all consuming components, which descend from the Provider React component, and they can access it using the Component component or using the useContext hook.
< AppContext.Provider value= {{ isLoggedIn, setIsLoggedIn }} > < div > < Header /> < Main /> < Footer /> </ div > </ AppContext.Provider >
It is important to use the context value judiciously as it can lead to complex and difficult to debug code. Therefore, you should not use a context value as global storage for your entire React application.
Restored as explained in the section below, it would be the best solution for managing a stateful global data store for your entire React application.
What is the difference between the Consumer component and the Usecontext hook?
Both the Consumer component and the useContext hook are ways of consuming data from a React context that you create in your ReactJS application using the Context API, however they differ in syntax and usage.
The Consumer component is provided by the createContext method, so it is a traditional way of consuming data, while useContext is a newer and simpler way of consuming data from the context.
Below is a comparison table explaining both: –
Consumer Component | UseContext Hook | |
React Version | >= 16.3 | >= 16.8 |
Use | Class Components | Functional components |
Syntax | Rendering prop | Hook |
Boiling plate | More | Any less |
Coupling | High | Low |
Performance | A little lower | A little higher |
Simplicity | Less simple | Simpler |
What are the use cases for Reactjs Context API?
This API can be useful in various scenarios for developing your React web application. However, common use cases are: –
Theme
One of the most common use cases for the React Context API is to manage different themes for your web application.
For example – providing and consuming theme color data such as light and dark. Users of your web app can switch between light and dark themes without reloading the page.
Authentication
Many ReactJS web developers use Context API to manage authentication.
By storing authentication data in a context, you can easily access it from any component of your React web app.
For example :-
import React, { createContext, useContext, useState } from "react"; const AuthContext = createContext; export default function App { const (isLoggedIn, setIsLoggedIn) = useState(false); const login = => { setIsLoggedIn(true); }; const logout = => { setIsLoggedIn(false); }; return ( < AuthContext.Provider value= {{ isLoggedIn, login, logout }} > {isLoggedIn ? < LogoutComponent /> : < LoginComponent />} </ AuthContext.Provider > ); } function LoginComponent { const { login } = useContext(AuthContext); return ( < div > < h1 >Login Page</ h1 > < button onClick={login}>Login</ button > </ div > ); } function LogoutComponent { const { logout } = useContext(AuthContext); return ( < div > < h1 >Logout Page</ h1 > < button onClick={logout}>Logout</ button > </ div > ); }
Location
You can use the React Context API to manage localization in your web application. By storing language data in context, you can easily switch between different languages and update the text in your app without having to reload the page.
Managing the State
You can use the React Context API to manage state by creating a Context and providing it with child components in your web application.
For example, let's convert our 'counter' example created at the beginning of this article, using the API:-
import React, { createContext, useContext, useState } from "react"; const CountContext = createContext ; function Counter { const (count, setCount) = useState(0); return ( <CountContext.Provider value= {{ count, setCount }} > <CountDisplay /> <CountButton /> </CountContext.Provider> ); } function CountDisplay { const { count } = useContext(CountContext); return <p>Count: {count}</p>; } function CountButton { const { setCount } = useContext(CountContext); function increment { setCount((prevCount) => prevCount + 1); } return <button onClick={increment}>Increment</button>; }
A popular alternative to the Context API for managing data state in your React application is Redux.
Redux is a library that offers a more robust and complex way of managing the state of your application.
While Context API is a simpler and more lightweight solution, Redux can offer more advanced features such as time travel debugging and middleware.
Context API vs Redux for State Management
React Context API | Restored | |
State management | Good for small to medium sized applications | Good for large, complex applications |
Performance | Fast and light | There may be performance issues with large states |
Scalability | Not as scalable as Redux | Highly scalable |
Standard code | Minimum | Requires more boilerplate code |
Tool support | Good | Great |
Learning curve | Easy to learn | Steep learning curve |
What are the disadvantages of using the React Context API?
Although using the React Context API gives us many advantages when sharing data and managing the state of components, it still has some disadvantages.
The table below lists the most common disadvantages of the React Context API, as well as suggesting a solution for each: –
Disadvantage | Solution |
Difficult to test | Use dependency injection or create a custom hook that consumes the context |
Unpredictable | Use gated React components and make state changes explicit |
Can lead to coupling | Use a global state management tool like Redux or the Context/Reducer pattern |
May lead to performance issues due to re-rendering | Use memorization techniques or avoid sharing state when possible |
Difficult to reason about | Use clear naming conventions and limit the scope of context usage |
Conclusion
In this article, I explained how to pass data into a component tree in a ReactJS web application using a method called 'prop drilling' which has some disadvantages like boilerplate code, code coupling, performance issues, etc.
These disadvantages can be overcome by using the React Context API, which uses the Provider-Consumer framework to pass data into a tree of components along with state management.
Additionally, I mentioned some common use cases where using React Context API proves to be more beneficial than using props.
However, using Context API also has some disadvantages like re-rendering, etc., which can be overcome using solutions discussed in this article.
If you liked this, be sure to check out our other articles on React.
- Top 13 React Charting and Charting Libraries
- Top 6 React IDEs and Editors
- React best practices for better ReactJS apps
- React Force Rendering: Complete Tutorial
- React lifecycle: methods and hooks in detail
Source: BairesDev