React Context API From Scratch A-Z

kirill ibrahim
Geek Culture
Published in
7 min readFeb 6, 2022

--

Banner of Article

Introduction:

Over the past years, Many react projects used the Redux state management library. Redux is a great tool for many react projects especially with the global state for the whole application, handle API requests in Action creators using redux-thunk or redux-saga, also Redux has other libraries associated with it like redux-persist which allows us to save/store data in localStorage and rehydrate on refresh.

But As React developer, I asked myself, what if I do not need all the great features that redux provides, I want just to use Redux only to avoid passing props down to deeply nested components.

The React Context API has been introduced, Since React’s version 16.3.0 it becomes safe to use in production. The context API can help to pass props down deeply to nested components or trees of components. I do not need to use redux only for avoiding passing props down to deeply nested components.

We will explain in this article:

1-What is the React context?

2-Examples of when to use React context.

3-How can use React Context?

4- Class.contextType vs Context.Consumer with the class-based component.

5- useContext Hook with Function-based component.

6-Updating the Context.

7-Consuming Multiple Contexts.

8-Caveats.

What is the React context?

React context allows us to pass data down from parent to child component(s), and consume data in whatever component we need in our application without using props, So React context allows us to share data across our components more easily. React context is great when you are passing data that can be used in any component in your application.

Props drilling problems can be solved by the context: when props are passed down from parents to children.

Props drilling is the process of passing props down multiple levels to nested components, through components that don’t need them.

Here is an example of props drilling. The theme data in this application will be passed as a prop to our app’s components.

We are drilling the theme prop through multiple components that don’t require it right now.

Other than passing it down to its child component, the Header component does not need a theme. UserMenu, LoginMenu, and Menu should consume theme data directly.

We can avoid prop drilling entirely with React context since we don’t need to use props.

Examples of when to use React context:

You can hold inside the context:

  • Theme data (like dark or light mode)
  • Application configuration
  • Authenticated user name
  • User settings
  • Preferred language
  • A collection of services

The purpose of using the context is to allow your components to access some global data and re-render when that data changes.

Data should be placed on React context that does not need to be updated often, Why? Because context was not made as an entire state management system. It was made to make consuming data easier.

We can think of React context as the equivalent of global variables for our React components.

How can use React Context?

Context is an API that is built into React, so we can create and use context directly by importing React in any React file.

  1. We create context by the createContext method:
const myContext = React.createContext(defaultValue);

The createContextfunction accepts one optional argument: the default value. This default value can be helpful for testing components in isolation without wrapping them.

2. We wrap the context provider around our component tree.

3. Pass any data on our context provider using the value prop

4. We use context consumer to read data within any component in the tree.

Create file myContext.js:

App.js file:

Class.contextType vs Context.Consumer with the class based component:

From my previous example, we use Context.Consumerwith function-based components, I will use the same example with class-based components:

What is contextType?

React 16.6 introduced a new feature that allows class-based components to access Context without using the Context.Consumer component.

This allows React to automatically connect the class component to context and it gives us a new property this.context property, which we can use in any of the lifecycle methods including the render function.

There are two ways to use contextType :

1-The contextType property on a class can be assigned a Context object created by React.createContext(). Using this property lets you consume the nearest current value of that Context type using this.context.

2-If you are using the experimental public class fields syntax, you can use a static class field to initialize your contextType.

The following Example demonstrates the two ways, a big part of example from the official documentation:

It can be used only in class-based components, and it is recommended because it is shorter and easier, and context can be accessed anywhere.

The following example from above after replace myContext.Consumner with contextType :

The useContext Hook with Function-based component:

A new way to consume context became available in React 16.8 with the introduction of React hooks. You can now use the useContexthook to consume context.

React.useContext() can be used in place of render props to consume context at the top of our component.

Example:

The benefit of the useContext hook is that it makes our components more concise and allows us to create our own custom hooks.

Depending on your preference, you can either use the consumer component directly or the useContext hook.

Updating Context:

The following example will be about how to update context value, we will put some changes on App, and Header components:

Updating the Context from the Nested component:

React Context API is stateless by default, and consumer components do not have a dedicated method to update the context value. In this case, you can pass a function down through the context to allow consumers to update the context, By integrating a state management mechanism (such as useState()), and including an update function right in the context next to the value, this can be easily implemented.

1-Updating my Context File:

Some modifications have happened to myContext.js file:

Here I’m setting the defaults for theme and a setThemefunction which will be sent by the context provider to the consumer(s). These are only defaults and I will provide their values when using the provider component in the parent App:

Note: the Context remains the same whether you use hooks or class-based components.

2-Setting parent state for dynamic context

Firstly, in order to have a dynamic context that can be passed to the consumers, I will use the parent’s state. This ensures that I have a single source of truth going forth. For example, my parent App will look like this:

import { myContext, themes } from "./myContext";
import React, { useState, useMemo } from "react";
const App = () => {
const [theme, setTheme] = useState(themes.light);
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<>
.......
</>
);
};

The theme is stored in the state. We will pass both theme and the setter function setTheme via context later.

3-Wrapping the consumer in a provider

Now in a myContext.povider I will pass the values which have to be sent via context to any level deeper. Here's how my parent App look like:

const App = () => {const [theme, setTheme] = useState(themes.light); 
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<>
<myContext.Provider value={value}>
<Header />
<div>
<h1>Hello World!</h1>
</div>
<Footer />
</myContext.Provider>
</>
);
}

Now, whenever the theme switcher is clicked it updates the context dynamically.

4-Creating a context consumer:

In order to have the themeSwitcher to set the theme, it should have the access to the theme setter function via context. It can look something like this:

The whole Example:

App.js file:

The <App /> memoizes the context value. Memoization preserves the context value object as long as the theme remains the same, preventing the re-rendering of consumers each time the <App /> re-renders.

Alternatively, const value = {theme, setTheme} would create different object instances during re-rendering of <App />, triggering context consumers to re-render. You can read more about Memoization.

Consuming Multiple Contexts:

If we have two contexts, one for theme mode, and the second one for login user information, The following example demonstrates, How we will consume them:

Some modifications have happened on myContext.js file:

App.js File:

If you prefer to use the consumer component directly, The following Example demonstrates How we will consume two contexts with the consumer component directly, I will modify UserMenu component:

Caveats:

As context uses reference identity to determine when to re-render, there are some gotchas that could cause unintentional renders in consumers when a provider’s parent re-renders.

When you pass an object down to your React context provider and any property on it updates, what happens? All components consuming that context will re-render. Smaller apps with few state values that aren’t updated very often (such as theme data) may not experience this problem.

This is a problem if our application has a lot of components in its component tree and you will need to perform many state updates.

Example:

Every time the Provider re-renders, the code below will re-render all consumers because a new object is always created for userContext.provider value:

const App = () => {  
const [theme, setTheme] = useState(themes.light);
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<>
<myContext.Provider value={value}>
<userContext.Provider value={{name: "kirill"}>
<Header />
<div>
<h1>Hello World!</h1>
</div>
<Footer />
</userContext.Provider>
</myContext.Provider>
</>
);
};

We will solve it by putting the value of userContext.providerinto the parent’s state:

const App = () => {  
const [userName, setUserName] = useState("kirill");
return (
<>
<userContext.Provider value={userName}>

</userContext.Provider>
</>
);
};

If you like my content, please follow me over Medium, I will need your support. I’m active on Twitter @IbraKirill.

If you enjoy reading the article and want to support me as a writer, you can buy me a coffee!

If you want to Dive into Context, Hooks, Redux . I advise you with the following Course.

--

--

kirill ibrahim
Geek Culture

I am a Web Front-End Engineer with 8 years of commercial experience working in various web-related roles. https://kirillibrahim.work/ My Twitter: @IbraKirill