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:
GrandParentandParentdon’t useuserThey just pass it down to
Child
This is prop drilling, which can get messy in larger apps.
Why Prop Drilling Is a Problem
Cluttered intermediate components
Components that don’t need the data still carry props
Difficult maintenance
Adding a new piece of shared state may require updating many components
Poor readability
Nested props chains make it hard to see where the state originates
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.