Mastering React Hooks: useContext

Mastering React Hooks: useContext

React is a popular frontend library (or framework, depending on your perspective) and an in-demand skill if you want to advance your career as a Frontend Developer. Apart from understanding the core of React, one must have a good understanding of various React hooks.

React currently has 15 hooks, of which we mostly use two: useState and useEffect. However, there are other hooks that are extremely useful in a variety of situations.

One such hook, the useContext hook, is one of the most important hooks to understand. The hook provides a native state management solution that can potentially replace any type of state management library.

Interesting, isn’t it?

Let’s get right into it and learn more about this amazing React hook 🚀

What is a Context?

Assume you’re in a room with some candies. There is now a rule that only people who are inside a specific room can eat candies from that room.

A room with some candies
A room with some candies

Now that you’re inside the room, you can enjoy those candies. A person who is outside your room or inside another room is not allowed to eat candies from your room.

Only people who are inside a specific room can eat candies from that room.
Only people who are inside a specific room can eat candies from that room.

Assume there is a room A that contains another room B within it. Both rooms are filled with candies. Varun and Pooja are the only two people present, where Varun is located in room A, and Pooja is located in room B (See the figure below).

Varun is located in room A, and Pooja is located in room B
Varun is located in room A, and Pooja is located in room B

Now, answer the following question – Can Varun eat candies from room B?

No, as the rule states, you cannot eat candies from a room that you are not in.

Varun cannot eat candies from the room B
Varun cannot eat candies from the room B

But, can Pooja eat candies from room A?

Yes, she can. It’s because room B is in room A. Therefore, Pooja is technically inside Room A, where she can eat the candies 🍬

Pooja can eat candies from the room A
Pooja can eat candies from the room A

But, what is a “Context”?

Contexts are nothing more than these rooms, and the candies represent the values that the context possesses. These values could be state variables, functions, or something else. Varun and Pooja are child components within the Context, so they (or the child components) have access to the values of their respective components.

The context contains some values that can be accessed by its child components using the useContext hook
The context contains some values that can be accessed by its child components using the useContext hook

Hmm, that was simple! Let’s go a step further and learn how to create and use contexts within our React apps.

Creating A Context In React

To create a new context, you have to use the React.createContext() method. It creates and returns the new context object.

const Context = React.createContext();Code language: JavaScript (javascript)

The new context includes a Provider component, which wraps all code that requires information inside the context. The Provider component accepts a value prop containing the information to which we want to give access.

const ContextProvider = () => {
  return (
    <Context.Provider value={{ counter: 10 }}>
      <Counter />
    </Context.Provider>
  );
};Code language: JavaScript (javascript)

The amazing thing about context is that everything inside the Provider component, including the child components, their children, and so on, has access to the variables contained within the value prop.

Everything inside the context provider component has access to the variables contained within the value prop
Everything inside the context provider component has access to the variables contained within the value prop

We can even create state variables, functions, and other types of objects within our ContextProvider component and pass them to the value prop.

const ContextProvider = () => {
  const [counter, setCounter] = useState(0);

  const increase = () => {
    setCounter(counter + 1);
  };

  const decrease = () => {
    setCounter(counter - 1);
  };

  return (
    <Context.Provider value={{ counter, increase, decrease }}>
      <Counter />
    </Context.Provider>
  );
};Code language: JavaScript (javascript)

Now, let’s look at how to access these values within the context’s child component.

Accessing The Values Inside Components

Now comes the part you’ve been waiting for, i.e., the useContext hook in action.

The useContext hook accepts a context object (returned by the React.createContext method) as an argument and returns the variables (inside the Provider component’s value prop).

We can use variables within our components, and whenever these variables change, all components that use these variables will rerender to display the updated UI.

It’s as simple as that!

Let’s just use it to see things in action. In our previous example, we had a Counter component as a child of the Provider, which means we can access the value of the counter state, as well as the increase and decrease functions, from the Counter component. Here’s how it’s done:

// Context.jsx

import React, { useState } from 'react';

export const Context = React.createContext();

export const Counter = () => {
  const { counter, increase, decrease } = useContext(Context);

  return (
    <div>
      <h2>Counter: {counter}</h2>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>Decrease</button>
    </div>
  );
};

export const ContextProvider = () => {
  const [counter, setCounter] = useState(0);

  const increase = () => {
    setCounter(counter + 1);
  };

  const decrease = () => {
    setCounter(counter - 1);
  };

  return (
    <Context.Provider value={{ counter, increase, decrease }}>
      <Counter />
    </Context.Provider>
  );
};Code language: JavaScript (javascript)
// App.jsx

import React from 'react';
import { ContextProvider } from './Context.jsx';

function App() {
  return <ContextProvider />;
}

export default App;
Code language: JavaScript (javascript)

Now, by clicking the “Increase” and “Decrease”, our counter state updates, and so does our Counter component.

Creating a counter using contexts and useContext hook
Creating a counter using contexts and useContext hook

Not only the Counter component but all components within the Provider can access all the variables in a similar way.

Why use Context when I can just use the useState hook?

That’s a valid question because, in the end, we’re just using the useState hook inside the context, so why not use it directly inside the main component and pass it down to the children?

There are two main reasons for not using the useState hook:

  1. You must manually pass down all state variables and functions to each child, and then to their child, and so on. This can quickly become tedious; you must also pass down the states when creating a new component.
  2. It causes unnecessary rerenders when the state changes. When any of the state variables change, the parent component rerenders, which rerenders all of their children. This could result in a significant performance bottleneck.

Therefore, using the useState hook directly on top of any component and passing it down to children is not a good idea. Context is only intended for such situations.

The Context Magic

As we just discussed, using a useState hook on top of the parent component results in unwanted renders of the children, even if the child is not using the state.

But, how does context solves this problem?

Context solves this problem by rendering only the children who are accessing the context’s values. Consider the following example, which contains two child components within a context.

function App() {
  return (
    <ContextProvider>
      <SiteHeader />
      <Counter />
    </ContextProvider>
  );
}Code language: JavaScript (javascript)

The context value is used by the child component Counter, but not by the SiteHeader component.

const SiteHeader = () => {
  console.log('SiteHeader component rendered');

  return (
    <header>
      <h1>This is the demo of Contexts and the useContext hook</h1>
    </header>
  );
};

const Counter = () => {
  const { counter, increase, decrease } = useContext(Context);

  console.log('Counter component rendered');

  return (
    <div>
      <h2>Counter: {counter}</h2>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>Decrease</button>
    </div>
  );
};
Code language: JavaScript (javascript)

Therefore, when we increment the counter, only the Counter component renders, not the SiteHeader component.

Simple counter using React context
Simple counter using React context and the useContext hook

You can try it out using the Codedamn Playground embedded below:

Let’s go a little deeper into the component tree now. Let’s add another component CounterChild inside the Counter component and use the counter state value there.

const CounterChild = () => {
  const { counter, increase, decrease } = useContext(Context);

  console.log('CounterChild component rerendered');

  return (
    <div>
      <h3>Counter: {counter}</h3>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>Decrease</button>
    </div>
  );
};

const Counter = () => {
  console.log('Counter component rendered');

  return (
    <div>
      <h2>This is a Counter</h2>
      <CounterChild />
    </div>
  );
};Code language: JavaScript (javascript)

Try increasing the counter to see what all components are rendering. You’ll notice that only the component that uses the context value, i.e., the CounterChild, renders now.

Deeply nested counter using React context
Deeply nested counter using React context and the useContext hook

You can try it out it using the Codedamn Playground embedded below:

As you can see, only the components that use the context values render when those values change. It is the CounterValue component in this case.

So, this is the most important advantage of contexts that they prevent unnecessary re-rendering.

Applications of Contexts

Contexts are used to allow components to share a common state. As a result, it can be used for state management within React applications.

Let’s go over some of the scenarios where it comes in handy.

Authentication: Storing Details of LoggedIn User

It is very useful during authentication because it allows you to store the details of the currently logged-in user inside a context and wrap the entire application around that context.

// UserContext.jsx

import React, { useState } from 'react';

const UserContext = React.createContext();

const UserContextProvider = ({ children }) => {
  const [user, setUser] = useState();

  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
};Code language: JavaScript (javascript)
// index.jsx

import React from 'react';
import ReactDOMClient from 'react-dom/client';
import { UserContextProvider } from './UserContext';
import App from './App';

const root = ReactDOMClient.createRoot(document.getElementById('root'));

root.render(
  <UserContextProvider>
    <App />
  </UserContextProvider>
);Code language: JavaScript (javascript)

Because the entire app is contained within the provider, you can access the user details from any component. It enables you to change the UI depending on whether the user is logged in or not.

For example, if there is a user, display the username; otherwise, display a login button in the navbar.

// Navbar.jsx

const Navbar = () => {
  const { user } = useContext(UserContext);

  return (
    <nav>
      {user ? <h1>{user.name}</h1> : <button>Login</button>}
    </nav>
  );
};Code language: JavaScript (javascript)

Theming: Toggling Between Light/Dark Mode

Toggling between different theme modes requires you to keep track of the current theme mode. To update the UI, your entire application requires access to the theme variable. Context is the best option here.
Here is how you can implement it:

// ThemeContext.jsx

import React, { useState } from 'react';

const ThemeContext = React.createContext();

const ThemeContextProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};Code language: JavaScript (javascript)
// index.jsx

import React from 'react';
import ReactDOMClient from 'react-dom/client';
import { ThemeContextProvider } from './ThemeContext';
import App from './App';

const root = ReactDOMClient.createRoot(document.getElementById('root'));

root.render(
  <ThemeContextProvider>
    <App />
  </ThemeContextProvider>
);
Code language: JavaScript (javascript)

You can wrap the entire application within a context so that you can access the theme state from anywhere and make necessary changes to the UI when it changes.

For example, you may modify the background and font color by checking whether the theme is light or dark.

// About.jsx

import React from 'react';
import { ThemeContext } from './ThemeContext';

export default function About() {
  const { theme } = React.useContext(ThemeContext);

  return (
    <div
      style={{
        backgroundColor: theme === 'dark' ? 'black' : 'white',
      }}
    >
      <p
        style={{
          color: theme === 'dark' ? 'white' : 'black',
        }}
      >
        Lorem, ipsum dolor sit amet consectetur adipisicing elit. Eos neque
        reprehenderit tempore possimus. Unde quasi iure aliquid alias aspernatur
        dolorem?
      </p>
    </div>
  );
}Code language: JavaScript (javascript)

Complete State Management Solution

It is not necessary to use heavy-weight state management libraries like Redux to manage your application state when using contexts.

You can create multiple contexts for each type of data and nest them based on the access level.

In an e-commerce application, for example, you’ll need authentication, product data, order data, etc.

You can now create three different contexts for each category, namely authentication, products, and orders.

Create three different contexts for each category
Create three different contexts for each category

Now, all components, including products (to show recommendations to the user) and order contexts (to retrieve the specific user’s orders), require user details. Therefore, it’s kept on top.

The order component requires product data to show which products you’ve previously purchased, on the products page. Therefore, the product context is kept next. At last, we place the order context.

Nesting order of the three contexts

You can now access context values based on access levels, which means that all child components have access to the user details, product list, and orders list. The order context can use values from the product context but not vice-versa. Similarly, both components can access user information but not vice versa.

Accessing the context values based on access levels
Accessing the context values based on access levels

We just build a complete state management solution using just contexts. You imagine how powerful these are and what amazing things you can do with them.

Summary

Hooks are very powerful additions to React. Having a solid understanding of different React hooks is very beneficial when building a React application.

useContext is one of the most important hooks that everyone should be aware of. It offers a very powerful API for sharing states among different components.

It offers some amazing benefits such as having state access for any child component, avoiding unnecessary rerenders, not having to explicitly pass the state as props to any component, etc.

Contexts can be used for a variety of purposes, including authentication, switching between light and dark themes, and even as a complete state management solution.

If you’re still using prop drilling or bulky frameworks like Redux, it’s time to try contexts, and trust me, you’ll fall in love with it 😉

I’ll be back with more interesting React hooks in this special series – Master React Hooks, so stay tuned 🚀

This article hopefully provided you with some new information. Share it with your friends if you enjoy it. Also, please provide your feedback in the comments section.

Thank you so much for reading 😄

Sharing is caring

Did you like what Varun Tiwari wrote? Thank them for their work by sharing it on social media.

0/10000

No comments so far