Prop Drilling in React – Problems and Solutions (2025 Guide)

In React, data flows from parent to child via props.

When you need to pass data through multiple nested components, it can lead to prop drilling — passing props down layers that don’t use them, just to reach the component that needs them.

Prop drilling can make your code harder to maintain, read, and scale.

In this tutorial, you’ll learn:

  • What prop drilling is
  • Why it’s a problem
  • Solutions including Context API and custom hooks

What Is Prop Drilling?

Prop drilling occurs when:

  • A parent component has some state or data

  • You need that data in a deeply nested child

  • You pass it through intermediate components that don’t use it

Example of Prop Drilling

function GreatGrandParent() {
  const user = { name: "Alice", age: 25 };
  return <GrandParent user={user} />;
}

function GrandParent({ user }) {
  return <Parent user={user} />;
}

function Parent({ user }) {
  return <Child user={user} />;
}

function Child({ user }) {
  return <h2>Hello, {user.name}!</h2>;
}

export default GreatGrandParent;

Works fine, but notice:

  • GrandParent and Parent don’t use user

  • They just pass it down to Child

This is prop drilling, which can get messy in larger apps.

Why Prop Drilling Is a Problem

  1. Cluttered intermediate components

    • Components that don’t need the data still carry props

  2. Difficult maintenance

    • Adding a new piece of shared state may require updating many components

  3. Poor readability

    • Nested props chains make it hard to see where the state originates

  4. Scalability issues

    • In large apps, passing props multiple layers down can become unmanageable

Solutions to Prop Drilling

Use Context API

The Context API allows you to share state globally, avoiding the need to pass props manually through each component.

import React, { createContext, useContext } from "react";

const UserContext = createContext();

function GreatGrandParent() {
  const user = { name: "Alice", age: 25 };
  return (
    <UserContext.Provider value={user}>
      <GrandParent />
    </UserContext.Provider>
  );
}

function GrandParent() {
  return <Parent />;
}

function Parent() {
  return <Child />;
}

function Child() {
  const user = useContext(UserContext);
  return <h2>Hello, {user.name}!</h2>;
}

export default GreatGrandParent;

Now only Child consumes the context; intermediate components are clean.

Use Custom Hooks

You can create a custom hook to access shared state easily.

// useUser.js
import { useContext, createContext } from "react";

const UserContext = createContext();

export const useUser = () => useContext(UserContext);

export const UserProvider = ({ children, user }) => (
  <UserContext.Provider value={user}>{children}</UserContext.Provider>
);

Usage:

 
function Child() {
const user = useUser();
return <h2>Hello, {user.name}!</h2>;
}

 Cleaner, reusable, and hides context complexity.

Component Composition

Instead of passing props down, wrap the components with HOCs (Higher Order Components) or render props.

 
function withUser(Component, user) {
return () => <Component user={user} />;
}
  • Useful for isolated cases

  • Less common than Context for modern apps

State Management Libraries

For larger apps with lots of global state, use:

  • Redux

  • Zustand

  • Recoil
    These libraries replace prop drilling entirely for complex state.