DEV Community

Yuki Kasugai
Yuki Kasugai

Posted on

Let's use Context API in place of Prop Drilling in React.js!

Have you ever heard of Prop Drilling? For beginners of React.js learners, it is a need-to-know term.
When I created an App with React.js, I wanted to pass props down to the grandchild component from the parent component, so I also needed to pass the props to the child components, as it is a rule of using props.

According to the public documents,

React components use props to communicate with each other. Every parent component can pass some information to its child components by giving them props.

In the following code, I did Prop Drilling. I passed the props down to TodoList component and then, I passed those to Todo component.

// APP.js
import { TodoList } from "./components/TodoList/TodoList";

function App() {
  const watchingCheckBox = (id) => {
    // Some Operation
  };

  const handleRemoveTodo = (id) => {
    // Some Operation
  };

  const reEditTodoName = (id, name) => {
    // Some Operation
  };

  let filteredTodos = todos;
  // Some Operation


  return (
    <div className="App">
      <TodoList
        todos={filteredTodos}
        watchingCheckBox={watchingCheckBox}
        handleRemoveTodo={handleRemoveTodo}
        reEditTodoName={reEditTodoName}
      />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
// TodoList.jsx
import React from "react";
import Todo from "../Todo/Todo";

function TodoList({
  todos,
  watchingCheckBox,
  handleRemoveTodo,
  reEditTodoName,
}) {
  return todos.map((todo) => (
    <Todo
      todo={todo}
      key={todo.id}
      watchingCheckBox={watchingCheckBox}
      handleRemoveTodo={handleRemoveTodo}
      reEditTodoName={reEditTodoName}
    />
  ));
}

export default TodoList;
Enter fullscreen mode Exit fullscreen mode
// Todo.jsx
import { HiOutlineXMark } from "react-icons/hi2";
import React from "react";

function Todo({ todo, watchingCheckBox, handleRemoveTodo, reEditTodoName }) {

  const handleTodoClick = () => {
    watchingCheckBox(todo.id);
  };

  const handleRemoveClick = () => {
    handleRemoveTodo(todo.id);
  };

  const reEdit = () => {
    if (// Some Condition) {
      handleRemoveTodo(todo.id);
    } else {
      reEditTodoName(todo.id, reEditName);
    }
  };

  return (
    <div>
        <input
          type="checkbox"
          checked={todo.isCompleted}
          onChange={handleTodoClick}
        />{" "}
        <label>
          {todo.name}
        </label>
      <button onClick={handleRemoveClick}>
        <HiOutlineXMark  />
      </button>
    </div>
  );
}

export default Todo;
Enter fullscreen mode Exit fullscreen mode

It seems like a bucket brigade. If the props-passing hierarchy is shallow, that is fine, but as your application grows, the hierarchy gets deeper. Then, you must pass down props to multiple components, which is not a good way to write clean and reusable code so it is called Prop drilling.

What is Prop drilling?

Prop drilling is a pattern in React where data is passed down from one component to another component, even if the intermediate components do not need the data. This often occurs when data needs to be passed from a high-level parent component down to a low-level child component that is several levels deep in the component tree. In order to pass data from the parent component to the child component, props are used.

So what should we use in place of Prop Drilling?
It is the Context API!

What is Context API?

Context API is a feature in React that allows data to be passed down through the component tree without having to pass props manually at every level. It provides a way to share data among components without the need to explicitly pass the data as props to each component that needs it.

In the following code, I changed my code with Context API. I used createContext(), SomeContext.Provider and useContext(). I also combined two files(TodoList.jsx and Todo.jsx) into one file.

createContext

createContext lets you create a context that components can provide or read.

const SomeContext = createContext(defaultValue)
Enter fullscreen mode Exit fullscreen mode

SomeContext.Provider

Wrap your components into a context provider to specify the value of this context for all components inside:

function App() {
  const [theme, setTheme] = useState('light');
  // ...
  return (
    <ThemeContext.Provider value={theme}>
      <Page />
    </ThemeContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

useContext

useContext is a React Hook that lets you read and subscribe to context from your component.

const value = useContext(SomeContext)
Enter fullscreen mode Exit fullscreen mode

↓My code

// App.js
import { createContext } from "react";
import { TodoList } from "./components/TodoList/TodoList";

export const FilteredTodosContext = createContext();

export function App() {
  const watchingCheckBox = (id) => {
    // Some Operation
  };

  const handleRemoveTodo = (id) => {
    // Some Operation
  };

  const reEditTodoName = (id, name) => {
    // Some Operation
  };

  let filteredTodos = todos;
  // Some Operation


  return (
    <div className="App">
      <FilteredTodosContext.Provider
        value={{
          filteredTodos,
          watchingCheckBox,
          handleRemoveTodo,
          reEditTodoName,
        }}
      >
        <TodoList />
      </FilteredTodosContext.Provider>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
// TodoList.jsx 
import { HiOutlineXMark } from "react-icons/hi2";
import React, { useContext, createContext } from "react";
import { FilteredTodosContext } from "../../App";

const TodosContext = createContext();

export function TodoList() {
  const { filteredTodos } = useContext(FilteredTodosContext);

  return filteredTodos.map((todo) => (
    <TodosContext.Provider value={todo} key={todo.id}>
      <Todo />
    </TodosContext.Provider>
  ));
}

export function Todo() {
  const todos = useContext(TodosContext);

  const { watchingCheckBox, handleRemoveTodo, reEditTodoName } 
    = useContext(FilteredTodosContext);

  const handleTodoClick = () => {
    watchingCheckBox(todos.id);
  };

  const handleRemoveClick = () => {
    handleRemoveTodo(todos.id);
  };

  const reEdit = () => {
    if (// Some Condition) {
      handleRemoveTodo(todo.id);
    } else {
      reEditTodoName(todo.id, reEditName);
    }
  };

  return (
    <div>
        <input
          type="checkbox"
          checked={todo.isCompleted}
          onChange={handleTodoClick}
        />{" "}
        <label>
          {todos.name}
        </label>
      <button onClick={handleRemoveClick}>
        <HiOutlineXMark  />
      </button>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Context API simplifies data management by allowing components to access data without passing it through multiple layers of props. In addition, it allows for global state management, which is helpful when multiple components need to share state.
Therefore, many developers prefer to use Context API.

Top comments (0)