React developers often encounter scenarios where they must pass data/state from a top-level component to a deeply nested component. Prop drilling refers to transporting this data/state as props to the intended destination through intermediate components.
Consider a visual representation of the component tree for a simple furniture e-commerce site to understand the problem better:
Suppose the top-level App
Component has access to the state of our shopping cart; we want to update the state by changing the button color from green to grey whenever the user clicks the "Add to Cart"
button.
Let's implement the scenario above to understand it. Run the code and try clicking the "Add to Cart"
button.
import React from 'react'; require('./styles.css'); import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render( <App />, document.getElementById('root') );
Lines 6-14: We set the state of the shopping cart and define a method changeColor
that is responsible for changing state.
Line 23: We pass buttonColor
and changeColor
as props to ProductCard
component.
Line 38: The ProductCard
component then passes the props it received to the Button
component.
Line 50: The Button
component passes changeColor
props to the onClick
event. The event fires when the button is clicked and sets the state of button to color grey.
Line 51: The button element reflects the change in state by turning the backgroundColor
attribute from green to grey to signal that product has been added to cart.
As can be observed from the code in the example app above, this approach is redundant and cumbersome even in a simple app. We pass state/data manually through components that don't require it, making our code cluttered and difficult to maintain. In addition, we may accidentally rename props midway through this 'drilling' process and run into bugs. These issues are compounded for large-scale applications, thus making this process infeasible.
There are three alternative solutions to solve the problem we encountered above:
Redux: An external library that offers state management for React applications.
Context: An API that enables sharing inherently global data/state with components at different nesting levels.
Component composition: A technique which involves passing components to other components as props. It has two subcategories:
Container components
Specialized components
For our example above, using Redux would needlessly involve an external library, and the Context API would pose issues with reusability and performance when the application is scaled up. Hence, component composition is a viable solution. The other two solutions may cater to different use cases involving prop drilling.
The app below shows modified code that uses composition with container components:
import React from 'react'; require('./styles.css'); import ReactDOM from 'react-dom'; import App from './App.js'; ReactDOM.render( <App />, document.getElementById('root') );
Lines 17–24: We put our code structure directly in the top-level component: App
, which enables us to pass required props directly to Button
component.
Line 33: We use the default Details
and Button
components within ProductCard
.
There are different ways to solve the issue of prop drilling. This article focused on component composition as a solution due to its effectiveness and simplicity for most use cases.