What is prop drilling?

👨‍💻 Frontend Developer 🟠 May come up 🎚️ Easy
#React

Brief Answer

Prop drilling is passing data through props from parent component to child component through multiple nesting levels, even when intermediate components don’t use this data:

  1. Problem — redundant prop passing 📤
  2. Complication — refactoring and code maintenance 🔧
  3. Solutions — Context API, component composition 🛠️
  4. Alternatives — state management libraries 📚
// Prop drilling example
function App() {
  const user = { name: "John" };
  return <Header user={user} />;
}
 
function Header({ user }) {
  return <Navigation user={user} />;
}
 
function Navigation({ user }) {
  return <UserProfile user={user} />;
}

Full Answer

Prop drilling is like passing a letter through a chain of people, where each person just passes it along without reading! This is a common problem in React applications. 📮

What is Prop Drilling?

Prop drilling occurs when data needs to be passed from a top-level component to a deeply nested one through intermediate components:

function App() {
  const theme = "dark";
  const user = { name: "Anna", role: "admin" };
  
  return <Layout theme={theme} user={user} />;
}
 
function Layout({ theme, user }) {
  return (
    <div className={theme}>
      <Sidebar user={user} />
    </div>
  );
}
 
function Sidebar({ user }) {
  return <UserMenu user={user} />;
}
 
function UserMenu({ user }) {
  return <span>Hello, {user.name}!</span>;
}

Prop Drilling Problems

1. Redundant code

// Components pass props they don't use
function MiddleComponent({ data, onAction, theme, user }) {
  return <DeepComponent data={data} onAction={onAction} theme={theme} user={user} />;
}

2. Refactoring complexity

// When changing structure, need to update all intermediate components
function App() {
  const newProp = "value"; // Added new prop
  return <Level1 existingProp={existingProp} newProp={newProp} />;
}

3. Single responsibility principle violation

// Component knows about data it doesn't need
function Header({ user, theme, settings, notifications }) {
  return <Navigation user={user} theme={theme} settings={settings} />;
}

Prop Drilling Solutions

1. Context API

import { createContext, useContext } from 'react';
 
const UserContext = createContext();
 
function App() {
  const user = { name: "Peter", role: "user" };
  
  return (
    <UserContext.Provider value={user}>
      <Layout />
    </UserContext.Provider>
  );
}
 
function UserMenu() {
  const user = useContext(UserContext);
  return <span>Hello, {user.name}!</span>;
}

2. Component composition

function App() {
  const user = { name: "Maria" };
  
  return (
    <Layout>
      <UserMenu user={user} />
    </Layout>
  );
}
 
function Layout({ children }) {
  return <div className="layout">{children}</div>;
}

3. Custom Hooks

function useUser() {
  return useContext(UserContext);
}
 
function UserProfile() {
  const user = useUser();
  return <div>{user.name}</div>;
}

Practical Examples

Theme problem

// ❌ Prop drilling
function App() {
  const theme = "light";
  return <Page theme={theme} />;
}
 
function Page({ theme }) {
  return <Button theme={theme} />;
}
 
// ✅ Context solution
const ThemeContext = createContext();
 
function App() {
  return (
    <ThemeContext.Provider value="light">
      <Page />
    </ThemeContext.Provider>
  );
}
 
function Button() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Button</button>;
}

State management

// ❌ Passing handlers
function Form() {
  const [data, setData] = useState({});
  
  const handleChange = (field, value) => {
    setData(prev => ({ ...prev, [field]: value }));
  };
  
  return <FormSection onChange={handleChange} />;
}
 
// ✅ useReducer + Context
const FormContext = createContext();
 
function Form() {
  const [state, dispatch] = useReducer(formReducer, {});
  
  return (
    <FormContext.Provider value={{ state, dispatch }}>
      <FormSection />
    </FormContext.Provider>
  );
}

When Prop Drilling is Acceptable

Shallow nesting

// For 1-2 levels might be simpler
function Parent() {
  const data = "important data";
  return <Child data={data} />;
}
 
function Child({ data }) {
  return <GrandChild data={data} />;
}

Well-defined interfaces

interface HeaderProps {
  user: User;
  onLogout: () => void;
}
 
function Header({ user, onLogout }: HeaderProps) {
  return <UserMenu user={user} onLogout={onLogout} />;
}

Context Alternatives

State Management libraries

// Redux, Zustand, Jotai
import { useStore } from './store';
 
function UserProfile() {
  const user = useStore(state => state.user);
  return <span>{user.name}</span>;
}

Render Props

function UserProvider({ children }) {
  const user = { name: "Alex" };
  return children(user);
}
 
function App() {
  return (
    <UserProvider>
      {(user) => <UserProfile user={user} />}
    </UserProvider>
  );
}

Best Practices

  1. Analyze depth — for 1-2 levels prop drilling might be simpler 📏
  2. Use Context — for data used in different parts of the tree 🌳
  3. Group data — pass objects instead of separate fields 📦
  4. Apply composition — use children and render props 🧩

Common Mistakes

Wrong:

// Excessive Context usage
const NameContext = createContext();
const AgeContext = createContext();
const EmailContext = createContext();

Correct:

// Group related data
const UserContext = createContext();

Conclusion

Prop drilling is a natural problem in React:

  • Problem — redundant prop passing through levels
  • Solutions — Context API, composition, state management
  • Choice — depends on nesting depth and data complexity
  • Goal — clean and maintainable code

Use the right tools for each situation! 🎯