1. Blog
  2. Software Development
  3. Mastering React Context API: A Comprehensive Guide
Software Development

Mastering React Context API: A Comprehensive Guide

Unlock the full potential of the React Context API! Discover this powerful tool and simplify your code and performance.

BairesDev Editorial Team

By BairesDev Editorial Team

BairesDev is an award-winning nearshore software outsourcing company. Our 4,000+ engineers and specialists are well-versed in 100s of technologies.

16 min read

Featured image

ReactJS is a widely used frontend web development framework written in Javascript. It was developed at Facebook and released in 2013 for all developers as an open-source framework for building dynamic and reactive web UIs.

A reactive web UI is a web application view that is automatically updated 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 whenever 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 above example at https://codesandbox.io/s/green-brook-0354fj

ReactJS Components and Virtual DOM

What Are React Components?

A ReactJS component is a piece of Javascript code that can be used as a self-contained building block to build front-end web applications.

In ReactJS you create reusable web components which can be rendered any number of times on a web page and are then reactively re-rendered on any data update or user input.

ReactJS updates these components without requiring a complete page refresh by leveraging its virtual DOM concept.

What Is a Virtual Dom?

Virtual DOM is a concept where a “virtual” representation of the user interface (HTML Document) is stored in the memory and kept synchronized with the “real” DOM using a library like ReactDOM.

A Reactjs Component Example 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 above ReactJS component is a JS function taking a single argument called props and returning a <button /> HTML tag with the onClick property and some text.

The button label is displayed as the button text, and when the button is clicked, the onClick function is called.

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 am rendering the same component twice but with different props(label).

You can test the above example at https://codesandbox.io/s/distracted-feynman-45vnuh

In the above screenshot you can also see the Virtual DOM on the bottom right side (highlighted in SkyBlue).

How to Pass Data From One Reactjs Component to Another?

In the examples above we created our ReactJS apps with only a single component whether it was the counter app or the click me app.

However, in a real-world ReactJS web application, we usually have multiple components interacting with each other through a data-passing methodology called ‘prop drilling’.

What Are Props?

Just like we can pass data to a JS function by using a feature called ‘arguments’, similarly, we can pass data to a ReactJS component by using a feature called props which is short for properties to do some operations on these and then return us a reactive UI.

See the click me app example above where we are passing label text and onClick event data as props to our <Button /> component.

What is Prop Drilling?

When you pass props down through multiple levels of deeply nested components in a ReactJS web application, then this is known as prop drilling.

For example:-

import React from 'react';

function App() {
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>&copy; 2023 My App</footer>;
}

In the above example we have a ReactJS app where the data const isLoggedIn is passed to the components Header, and Main, and also to the 3rd level components Profile and Settings through an intermediary component Main using props and prop drilling methodology.

What Is a Component Tree?

A component tree in a ReactJS application is a representation of components arranged in a hierarchy.

This hierarchical structure is built by nesting components inside one another, with the top-level component being the root and the child components located below it.

The above image represents a component tree from the prop drilling example mentioned in the previous section.

Disadvantages of Passing Data Using Prop Drilling in a Component Tree 

There are times when it’s best to avoid prop drilling as it does have it’s disadvantages:

Data passing flow: By using props you need to pass props at each level in a component tree.

Complexity: The complexity of drilling can increase with the growth and complexity of your ReactJS web application, making it harder for you to maintain and debug it.

Boilerplate code: Prop drilling requires you to write more code during React application development to pass down 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, meaning that changes in one component can affect other components, making the codebase of your app more fragile.

Performance issues: Passing down large or complex data through multiple levels of components using props can negatively impact your ReactJS app’s performance.

React Context API

To develop ReactJS web applications more efficiently by helping developers resolve the above disadvantages ReactJS core team introduced, in version 16.3, an API called Context API using which you can pass data down in a component tree without props.

With this API in ReactJS, you can share data between components, in a component tree, that are not directly related to each other removing the need to manually pass props at each level.

What Are the Benefits of Using Context API Over Prop Drilling?

Using React Context API over prop drilling provides us benefits which are:-

Context API Prop drilling
Data Passing Flow Uses a provider-consumer model to pass data down the component tree Passes data down the component tree as props
Complexity Reduces complexity by eliminating the need for props to be passed down through intermediate components Can result in increased complexity as components need to be aware of props they don’t use and may have to pass them down to child components
Boilerplate Code Reduces boilerplate code by providing a centralized store of data Can result in increased boilerplate code as components need to pass down props to child components
Coupling Reduces coupling between components as components are no longer dependent on each other for data flow Can result in increased coupling as components need to be aware of the data they need to receive and may have to pass it down to child components
Performance Issues Provides a faster and more efficient way of passing data as it eliminates the need for props to be passed through intermediate components Can result in performance issues as props are passed down through multiple levels, leading to increased render times and potential memory leaks

Example

For example, in our component tree, shown in the previous section, by using ReactJS Context API we can pass down data directly from the App component to both Profile and Settings components without passing data through the Main component.

Let us see, below, the same prop drilling example re-written by 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>&copy; 2023 My App</footer>;
}

In the code above I first created a context called AppContext by using a method createContext() provided by the React Context API.

Then I provided this context, by returning the AppContext.Provider component with the ‘value’ prop, from my parent component App, to those child components with whom I want to share my data.

And, then finally I consumed data, by using the useContext(AppContext) hook, within my child components Header, Profile, and Settings.

As you can see we totally removed the need to pass data to the intermediary Main component making our ReactJS code more maintainable.

How to Use React Context API in Your React App?

There are 5 steps required to use React Context API while developing your Reactjs app:-

1. Create your context object by using the createContext() method

You need to create a context, by using the createContext() method, which will act as a data store. Note:- You can create any number of contexts in your React app.

const AppContext = createContext();

2. Provider and Consumer components

The AppContext object we created by using createContext() method above basically returns two components; the Provider to pass data down the component tree by using ‘value’ prop and the Consumer to read this passed data.

3. Wrap your component tree within the Provider component

Wrap your component tree with the Provider component. This should be done at the highest level of the component tree that needs access to the 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 like to the Provider component using the value prop. This value can be a string, object, or function, and will be available to all the consuming components.

<AppContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>

5. Read context data by using the Consumer component

Read the value of the context within any component that needs it by using the Consumer component. The Consumer component takes a function as a child and passes the current value of the context as an argument to that function. In our above example, however, we are using the useContext() hook to get data.

const { isLoggedIn } = useContext(AppContext);

What Is the Context Value?

As mentioned above in step 4, the Provider component in React Context API accepts one argument, the ‘value’ prop, which is basically a global data store also called the context value.

This context value is then shared across all the consuming components, which descend the Provider React component, and they can access it either by using the Component component or by using the useContext() hook.

<AppContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>

<div>

<Header />

<Main />

<Footer />

</div>

</AppContext.Provider>

It is important to use context value judiciously as it can lead to a complex and hard-to-debug code. Therefore, you must not use a context value as a global store for your whole React app.

Redux, as explained in a section below, would be the best solution to manage a global data store with state for your whole React app.

What Is the Difference Between the Consumer Component and Usecontext Hook?

Both the Consumer component and useContext() hook are ways to consume data from a React context you create in your ReactJS app using the Context API, however, they differ in syntax and usage.

The Consumer component is provided by the createContext() method thus it is a traditional way to consume data, while, useContext() is a newer and simpler way to consume data from context.

Below is a comparison table explaining these both:-

Consumer Component useContext() Hook
React Version >= 16.3 >= 16.8
Usage Class components Functional components
Syntax Render Prop Hook
Boilerplate More Less
Coupling High Low
Performance Slightly lower Slightly higher
Simplicity Less simple More simple

What Are the Use Cases of Reactjs Context API?

This API can be useful in a number of scenarios for your React web application development. Nonetheless, the common use cases are:-

Theming

One of the most common use cases for React Context API is to manage different themes for your web application.

For Example – providing and consuming theme colors data such as light and dark. Your web application users can switch between light and dark themes without reloading the page.

Authentication

Many ReactJS web developers use Context API for managing authentication.

By storing authentication data in a context you can easily access it from any component in your React web application.

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>
);
}

Localization

You can use React Context API to manage localization in your web application. By storing language data in the context, you can easily switch between different languages and update the text in your application without having to reload the page.

Managing State

You can use React Context API to manage a state by creating a Context and providing it the child components in your web app.

For example let us 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 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 in 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 Managing State

React Context API Redux
State management Good for small to medium-sized applications Good for large and complex applications
Performance Fast and lightweight Can have performance issues with large state
Scalability Not as scalable as Redux Highly scalable
Boilerplate code Minimal Requires more boilerplate code
Tooling support Good Excellent
Learning curve Easy to learn Steep learning curve

What Are the Disadvantages of Using React Context API?

Though using React Context API provides us many advantages while sharing data and managing state in components, however, it still has some disadvantages.

The table below lists the most common disadvantages of the React Context API along with suggesting a solution for each:-

Disadvantage Solution
Difficult to test Use dependency injection or create a custom hook that consumes the context
Unpredictable Use controlled React components and make state changes explicit
Can lead to coupling Use a global state management tool like Redux or the Context/Reducer pattern
Can lead to performance issues due to re-rendering Use memoization 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 have explained how to pass data down in a component tree in a ReactJS web application using a method called ‘prop drilling’ that has a few disadvantages such as boilerplate code, code coupling, performance issues, etc.

These disadvantages can be overcome by using React Context API which uses the Provider-Consumer framework for passing data down in a component tree along with managing the state.

Also, I have mentioned a few common use cases where React Context API’s use proves to be more beneficial than using props.

However, using Context API also has a few disadvantages such as re-rendering, etc which can be overcome by using solutions as discussed in this article.

If you enjoyed this, be sure to check out our other React articles.

Key Questions

Can I pass data from child to parent component using React Context API?
No, you can not pass data from child to parent in a React component tree whether you use prop drilling or React Context API. However, there is a sort of hack to achieve this by using React Context API as explained at https://forum.freecodecamp.org/t/react-context-api-pass-data-to-parent/467261

Can I use React Context API along with Redux in my app to manage state?
Yes, Context API can be used along with Redux for managing the state in your React app. Context API can be used to provide the Redux store to a child component without the need for passing it down via props.

Do I always have to use Context API or prop drilling to pass data down in a React component tree?
Not necessarily if we can compose our React components better. If we create those child components which only the topmost component, App, needs to know directly within the App component then doing so will reduce the unnecessary effort to pass data either by using prop drilling or by Context API from the App component to these child components.

Is the Context application programming interface deprecated?
No, Context API is not deprecated as it is still supported and actively maintained by the ReactJS core team.

Are React props mutable?
No, once props are passed down to a child component they cannot be modified directly. However, if a child component triggers a state change in its parent component through a call-back function then props can be modified indirectly.

Is React context mutable?
Yes, React context is mutable, however, you must use only specific methods such as the Provider component's value prop or useContext hook's return value.

Tags:
BairesDev Editorial Team

By BairesDev Editorial Team

Founded in 2009, BairesDev is the leading nearshore technology solutions company, with 4,000+ professionals in more than 50 countries, representing the top 1% of tech talent. The company's goal is to create lasting value throughout the entire digital transformation journey.

Stay up to dateBusiness, technology, and innovation insights.Written by experts. Delivered weekly.

Related articles

Software Development - 24+ React UI
Software Development

By BairesDev Editorial Team

16 min read

Contact BairesDev
By continuing to use this site, you agree to our cookie policy and privacy policy.