Questions
This application suffers from 'prop drilling.' How do you fix it?
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.
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>
);
} The Solution
// 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> ); }
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:
- Create a Context: Define a
ThemeContextusingReact.createContext(). - Provide the Context: Wrap the part of your component tree that needs access to the context with
ThemeContext.Provider. Pass thethemestate and thesetThemefunction (ortoggleThemefunction) as thevalueprop to the provider. - Consume the Context: In any component that needs the
theme(likeUserProfile), use theuseContexthook 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?