DEV Community

Cover image for Using React Context to Prevent Prop Drilling
Sho-ki
Sho-ki

Posted on • Updated on

Using React Context to Prevent Prop Drilling

In this article, I will summarize the usefulness and usage of React Context, which is useful for prop in React.

Overview

Problem

If you don’t use React Context and try to pass data to prop, you need to pass the data to prop and receive it again and again. And you won’t know what you are passing if the components are too nested.
For example, as shown in the figure below, when you want to pass the array data ‘languages’ in the green background to container 3, you can implement it in two patterns: with using React Context and without using React Context.

Pattern 1 (with using React Context)

with using React Context

Pattern 2 (without using React Context)

without using React Context

In the case of Pattern 2, where the React Context is not used, the data is passed in the following order: Background → Container1 → Container2 → Container3. Even though the data is only used in Container3, Prop is being passed again and again, called prop drilling.

The code for this looks like the following.

import "./App.css";

function App() {
  const languages = ["JavaScript", "Python", "Java", "Golang"];
  return (
    <div className="background">
      Background 
      <p className="languages">
        Pass languages[JavaScript, Python, Java, Golang] to Container 3
      </p>
      {/* languages ->  Container1*/}
      <Container1 languages={languages} />
    </div>
  );
}

export default App;

function Container1({ languages }) {
  return (
    <div className="container1">
      Container 1
      <Container2 languages={languages} />
      {/* Container1 ->  Container2 */}
    </div>
  );
}

function Container2({ languages }) {
  return (
    <div className="container2">
      Container 2
      <Container3 languages={languages} />
      {/* Container2 ->  Container3 */}
    </div>
  );
}

function Container3({ languages }) {
  return (
    <div className="container3">
      Container 3
      <ul className="languages-area">
        {languages.map((language, i) => (
          <li key={i}>{language}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Container1 and Container2 receive data and carry the data to Container3, even though they do not actually use the data.
This is not impossible to do, but if there are 10 layers of components, it will no longer be clear what data is being passed or received.

Solution

So you will use React Context to pass data directly from Background to Component3. It’s easy to use, and here’s an image of React Context.
React Context Image

Create a friend Mr. B who is a mutual friend of Mr. A and Mr. C, and pass and receive data through Mr. B.

Implementation

Step 1. import React Context

First, import the react context.
Then, use createContext() to declare a context called LanguageContext.
↑ The one created here is the so-called common friend Mr. B of Mr. A and Mr. C. He will receive and pass data.

import "./App.css";
import { createContext, useContext } from "react";

const LanguageContext = createContext();
Enter fullscreen mode Exit fullscreen mode

Step 2. Pass the data to Context

Using the Provider, pass the data you want to pass to the LanguageContext you just created.
This is like passing the data to your mutual friend B.

Note

・The range that can receive the data passed to LanguageContext is only the range enclosed by LanguageContext.
・The value to be written in LanguageContext.Provider must be ‘value’.

import "./App.css";
import { createContext, useContext } from "react";

const LanguageContext = createContext();

//  Added code below
function App() {
  const languages = ["JavaScript", "Python", "Java", "Golang"];
  return (
    <LanguageContext.Provider value={{ languages }}>
      <div className="background">
        Background 
        <p className="languages">
          Pass languages[JavaScript, Python, Java, Golang] to Container 3
        </p>
        <Container1 />
      </div>
    </LanguageContext.Provider>
Enter fullscreen mode Exit fullscreen mode

Step 3. Writing parts that are not related to data transfer

The code is cleaner than the code without React Context because there is no need to receive or pass prop.

import "./App.css";
import { createContext, useContext } from "react";

const LanguageContext = createContext();

function App() {
  const languages = ["JavaScript", "Python", "Java", "Golang"];
  return (
    <LanguageContext.Provider value={{ languages }}>
      <div className="background">
        Background 
        <p className="languages">
          Pass languages[JavaScript, Python, Java, Golang] to Container 3
        </p>
        <Container1 />
      </div>
    </LanguageContext.Provider>
  );
}

// Added code below
function Container1() {
  return (
    <div className="container1">
      Container 1
      <Container2 />
    </div>
  );
}

function Container2() {
  return (
    <div className="container2">
      Container 2
      <Container3 />
    </div>
  );
Enter fullscreen mode Exit fullscreen mode

Step 4. Receive data from Context

In the part of the component that you want to receive data (Container3 in this case), use useContext() to receive the data.
For arguments in useContext(), put context = LanguageContext. And it will be useContext(LanguageContext). Then, you can receive the data passed by the Provider.

import "./App.css";
import { createContext, useContext } from "react";

const LanguageContext = createContext();

function App() {
  const languages = ["JavaScript", "Python", "Java", "Golang"];
  return (
    <LanguageContext.Provider value={{ languages }}>
      <div className="background">
        Background 
        <p className="languages">
          Pass languages[JavaScript, Python, Java, Golang] to Container 3
        </p>
        <Container1 />
      </div>
    </LanguageContext.Provider>
  );
}

function Container1() {
  return (
    <div className="container1">
      Container 1
      <Container2 />
    </div>
  );
}

function Container2() {
  return (
    <div className="container2">
      Container 2
      <Container3 />
    </div>
  );

// Added code below
function Container3() {
  const ctx = useContext(LanguageContext);
  console.log(ctx.languages);
  return <p>{ctx.languages}</p>;
}
Enter fullscreen mode Exit fullscreen mode

Then, you are able to receive the data as you expect.
["JavaScript", "Python", "Java", "Golang"];

Step5. Use the received data

Then, you just use the data as always. And this is the completed code.

import "./App.css";
import { createContext, useContext } from "react";

const LanguageContext = createContext();

function App() {
  const languages = ["JavaScript", "Python", "Java", "Golang"];
  return (
    <LanguageContext.Provider value={{ languages }}>
      <div className="background">
        Background 
        <p className="languages">
          Pass languages[JavaScript, Python, Java, Golang] to Container 3
        </p>
        <Container1 />
      </div>
    </LanguageContext.Provider>
  );
}

function Container1() {
  return (
    <div className="container1">
      Container 1
      <Container2 />
    </div>
  );
}

function Container2() {
  return (
    <div className="container2">
      Container 2
      <Container3 />
    </div>
  );

// Added code below
function Container3() {
  const ctx = useContext(LanguageContext);
  console.log(ctx.languages);
  return (
    <div className="container3">
      Container 3
      <ul className="languages-area">
        {ctx.languages.map((language, i) => (
          <li key={i}>{language}</li>
        ))}
      </ul>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Note

Normally, you would create a folder called “store” and create the LanguageContext.js in it. And create a context and export it, but in this case, for ease of viewing, I’ve included everything in the same file.

Summary

These are the steps to use React Context:
Step1. import React Context, and create React Context
Step2. Pass the data to Context
Step3. Write components whatever you want
Step4. Receive data from Context
Step5. Use the received data

The code I wrote in this post including css is in Github

Discussion (0)