DeployU
Interviews / Frontend Engineering / This application suffers from 'prop drilling.' How do you fix it?

Questions

This application suffers from 'prop drilling.' How do you fix it?

practical State Management Interactive Quiz Code Examples

You’re working on a React application with a UserProfile component that needs to display the user’s preferred theme. The theme state (e.g., ‘light’ or ‘dark’) is managed at the top-level App component.

To get the theme down to UserProfile, it’s currently being passed as a prop through several intermediate components: App -> Dashboard -> Settings -> UserProfile. The Dashboard and Settings components don’t actually use the theme prop themselves; they just pass it along. This is a classic case of prop drilling.

This makes the code verbose, harder to read, and more difficult to refactor, as changes to the theme prop would require modifying multiple intermediate components.

The Challenge

Your task is to refactor the application to eliminate prop drilling. Use React Context to provide the theme and a function to setTheme directly to the UserProfile component (and any other component that needs it), bypassing the intermediate components.

The application’s functionality (toggling the theme and displaying it) should remain identical.

Your Code
import React, { useState } from 'react';

// Component that needs the theme
function UserProfile({ theme }) {
  return (
    <div style={{ border: '1px solid #ccc', padding: '10px', marginTop: '10px' }}>
      <h3>User Profile</h3>
      <p>Current Theme: {theme}</p>
    </div>
  );
}

// Intermediate component 1 (doesn't use theme, just passes it)
function Settings({ theme }) {
  return (
    <div style={{ border: '1px solid #eee', padding: '10px', margin: '10px' }}>
      <h4>Settings Component</h4>
      <UserProfile theme={theme} />
    </div>
  );
}

// Intermediate component 2 (doesn't use theme, just passes it)
function Dashboard({ theme }) {
  return (
    <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}>
      <h2>Dashboard Component</h2>
      <Settings theme={theme} />
    </div>
  );
}

export default function App() {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <div style={{ padding: '20px', background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
      <h1>App Component</h1>
      <button onClick={toggleTheme}>Toggle Theme</button>
      <Dashboard theme={theme} /> {/* Prop drilling starts here */}
    </div>
  );
}
Click "Run Code" to see output...
Click "Run Code" to see test results...

The Solution

Wrong Approach

// Component that needs the theme function UserProfile({ theme }) { return ( <div style={{ border: '1px solid #ccc', padding: '10px', marginTop: '10px' }}> <h3>User Profile</h3> <p>Current Theme: {theme}</p> </div> ); } // Intermediate component 1 (doesn't use theme, just passes it) function Settings({ theme }) { return ( <div style={{ border: '1px solid #eee', padding: '10px', margin: '10px' }}> <h4>Settings Component</h4> <UserProfile theme={theme} /> </div> ); } // Intermediate component 2 (doesn't use theme, just passes it) function Dashboard({ theme }) { return ( <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}> <h2>Dashboard Component</h2> <Settings theme={theme} /> </div> ); } export default function App() { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; return ( <div style={{ padding: '20px', background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}> <h1>App Component</h1> <button onClick={toggleTheme}>Toggle Theme</button> <Dashboard theme={theme} /> {/* Prop drilling starts here */} </div> ); }

Right Approach

import React, { useState, useContext, createContext } from 'react'; // 1. Create a ThemeContext const ThemeContext = createContext(); // Component that needs the theme - now consumes context directly function UserProfile() { const { theme } = useContext(ThemeContext); // Consume theme from context return ( <div style={{ border: '1px solid #ccc', padding: '10px', marginTop: '10px' }}> <h3>User Profile</h3> <p>Current Theme: {theme}</p> </div> ); } // Intermediate component 1 - no longer needs to pass theme prop function Settings() { return ( <div style={{ border: '1px solid #eee', padding: '10px', margin: '10px' }}> <h4>Settings Component</h4> <UserProfile /> {/* No theme prop needed */} </div> ); } // Intermediate component 2 - no longer needs to pass theme prop function Dashboard() { return ( <div style={{ border: '1px solid #ddd', padding: '10px', margin: '10px' }}> <h2>Dashboard Component</h2> <Settings /> {/* No theme prop needed */} </div> ); } export default function App() { const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light')); }; return ( // 2. Provide the theme and toggle function via ThemeContext.Provider <ThemeContext.Provider value={{ theme, toggleTheme }}> <div style={{ padding: '20px', background: theme === 'light' ? '#fff' : '#333', color: theme === 'light' ? '#000' : '#fff' }}> <h1>App Component</h1> <button onClick={toggleTheme}>Toggle Theme</button> <Dashboard /> {/* No theme prop needed */} </div> </ThemeContext.Provider> ); }

Why This Works

Prop drilling is an anti-pattern where props are passed down through multiple levels of components in a component tree, even if those intermediate components don’t actually need the data. This makes the code verbose, harder to read, and more difficult to maintain because any change to the prop’s name or type requires updating all intermediate components.

The Fix: Using React Context

React Context provides a way to share values like theme or user authentication status between components without having to explicitly pass a prop through every level of the tree. It’s designed for “global” data that many components might need.

Here’s how to refactor using Context:

  1. Create a Context: Define a ThemeContext using React.createContext().
  2. Provide the Context: Wrap the part of your component tree that needs access to the context with ThemeContext.Provider. Pass the theme state and the setTheme function (or toggleTheme function) as the value prop to the provider.
  3. Consume the Context: In any component that needs the theme (like UserProfile), use the useContext hook to directly access the values from the context.

By using ThemeContext, we’ve eliminated the need to pass the theme prop through Dashboard and Settings. UserProfile now directly accesses the theme from the context, making the component tree cleaner and more maintainable.

Practice Question

What is the primary solution for eliminating 'prop drilling' in a React application?