Prop Drilling with Components thumbnail image
Neville Kati /

~ min read

Prop Drilling with Components

A common challenge developers encounter in the world of React development is "Prop drilling". The aim of this aricle is to take a look at what it is, how to use it and how to avoid common problems with it.

Read more about Props here

What is Prop Drilling

Prop drilling (also refered to as Component Chaining) is the process through which data is passed through multiple layers of the component tree from parent to child components, even when some of those components do not need/use the props.

Consider a scenario where a top-level component that fetches data from an API and needs to pass this data down to multiple nested child components. It might happen that you will have to pass this through several components. This passing down of props through multiple levels of components is what prop drilling is.

An Example

Let us look at a component with some text that displays the number of times a button is clicked and button that handles the click.

import React, { useState } from 'react'

// ParentComponent

function ParentComponent() {
  const [clickCount, setClickCount] = useState(0)

  function handleButtonClick() {
    setClickCount(clickCount + 1)
  }
  return (
    <>
      <h1>The button has been clicked {clickCount} times</h1>

      <button onClick={handleButtonClick}>Click</button>
    </>
  )
}

Let us break this into smaller components

import React, { useState } from 'react'

// ParentComponent

function ParentComponent() {
  const [clickCount, setClickCount] = useState(0)

  function handleButtonClick() {
    setClickCount(clickCount + 1)
  }
  return <Counter clickCount={clickCount} onClick={handleButtonClick} />
}

// Child Component

function Counter({ onClick, clickCount }) {
  return (
    <>
      <h1>The button has been clicked {clickCount} times</h1>

      <button onClick={onClick}>Click</button>
    </>
  )
}

The values for clickCount and the handleButtonClick are passed down as props to the Counter component. The Counter component can be broken down even further.

import React, { useState } from 'react'

// ParentComponent

function ParentComponent() {
  const [clickCount, setClickCount] = useState(0)

  function handleButtonClick() {
    setClickCount(clickCount + 1)
  }
  return <Counter clickCount={clickCount} onClick={handleButtonClick} />
}

// Child Component

function Counter({ onClick, clickCount }) {
  return (
    <>
      <CounterText clickCount={clickCount} />

      <CounterButton onClick={onClick} />
    </>
  )
}

// GrandChild Components

function CounterText({ clickCount }) {
  return <h1>The button has been clicked {clickCount} times</h1>
}

function CounterButton({ onClick }) {
  return <button onClick={onClick}>Click</button>
}

This is prop drilling. The props clickCount and handleButtonClick are passed down through several components before reaching the components that actually use them.

What problems can Prop drilling cause?

Our example above might not clearly communicate the dangers of prop drilling but as the codebase for your application grows larger, you may find yourself drilling through many layers of components. These are some issues your face:

Influence on Performance

Prop drilling can negatively Influence Performance especially if the props contain large amounts of data.

Each intermediary component in the heirachy has to re-render when the props change, potentially leading to unnecessary re-renders and impacting performance.

Complexity and decreased maintainability

Prop drilling can lead to increased complexity especially in large component trees, making it harder to refactor and hence more difficult to maintain.

How can we deal with issues relating to Prop drilling? 🤔

There are several techniques to overcome prop drilling in React.js:

  • Context API: React's Context API provides a way for data to be shared across components without passing props through each level of the heirachy.

  • State Management Libraries: Using state management libraries such as Redux or Zustand can help manage application state, hence reducing the need for prop drilling.

Let us refactor the code above to use context.

// MyContext.js

import React from 'react'

const MyContext = React.createContext()

export default MyContext
// ParentComponent.js

import React from 'react'
import Counter from './ChildComponent'
import MyContext from './MyContext'

function ParentComponent() {
  const [clickCount, setClickCount] = useState(0)

  function handleButtonClick() {
    setClickCount(clickCount + 1)
  }

  return (
    <MyContext.Provider value={{ clickCount, handleButtonClick }}>
      <Counter />
    </MyContext.Provider>
  )
}

export default ParentComponent
// Counter.js

import React from 'react'

function Counter() {
  return (
    <>
      <CounterText clickCount={clickCount} />
      <CounterButton onClick={onClick} />
    </>
  )
}

// GrandChild Components

import React, { useContext } from 'react'
import MyContext from './MyContext'

function CounterText() {
  const { clickCount } = useContext(MyContext)
  return <h1>The button has been clicked {clickCount} times</h1>
}

function CounterButton() {
  const { handleButtonClick } = useContext(MyContext)

  return <button onClick={handleButtonClick}>Click</button>
}

In this refactored example, we've used the Context API to provide and consume the countClick and handleButtonClick props without having to pass them down through every component manually.

Conclusion

Understanding the impact prop drilling can have on your application can greatly influence how you structure your codebase. By using the techniques mentioned above, you'll empower yourself to build clean, maintainable and scalable React applications. Good luck!