Prop Drilling
[POLL]: Do you know what I'm talking about when I refer to "the prop drilling problem" in #ReactJS? Yep Maybe Nope π
This twitter poll I started is still running. Please answer it before continuing!
The goal of this post is to not only help you understand what prop drilling is (some also refer to it as "threading"), but also when it can be a problem and mechanisms you can use to side-step or avoid it.
What is prop drilling?
Prop drilling (also called "threading") refers to the process you have to go through to get data to parts of the React Component tree. Let's look at a very simple example of a stateful component (yes, it's my favorite component example):
class Toggle extends React.Component {
state = {on: false}
toggle = () => this.setState(
({on}) => ({on: !on})
)
render() {
return (
<div>
<div>
The button is {this.state.on ? 'on' : 'off'}
</div>
<button onClick={this.toggle}>
Toggle
</button>
</div>
)
}
}
Let's refactor this into two components now:
class Toggle extends React.Component {
state = {on: false}
toggle = () => this.setState(
({on}) => ({on: !on})
)
render() {
return (
<Switch
on={this.state.on}
onToggle={this.toggle}
/>
)
}
}
function Switch({on, onToggle}) {
return (
<div>
<div>
The button is {on ? 'on' : 'off'}
</div>
<button onClick={onToggle}>
Toggle
</button>
</div>
)
}
Simple enough, the Switch
needs a reference to the toggle
and on
state, so we're sending some props there. Let's refactor it once more to add another layer in our component tree:
class Toggle extends React.Component {
state = {on: false}
toggle = () => this.setState(
({on}) => ({on: !on})
)
render() {
return (
<Switch
on={this.state.on}
onToggle={this.toggle}
/>
)
}
}
function Switch({on, onToggle}) {
return (
<div>
<SwitchMessage on={on} />
<SwitchButton onToggle={onToggle} />
</div>
)
}
function SwitchMessage({on}) {
return (
<div>
The button is {on ? 'on' : 'off'}
</div>
)
}
function SwitchButton({onToggle}) {
return (
<button onClick={onToggle}>
Toggle
</button>
)
}
This is prop drilling. To get the on
state and toggle
handler to the right places, we have to drill (or thread) props through the Switch
component. The Switch
component itself doesn't actually need those values to function, but we have to accept and forward those props because it's children need them.
Why is prop drilling good?
Did you ever work in an application that used global variables? What about an AngularJS application that leveraged non-isolate $scope
inheritance? The reason that the community has largely rejected these methodologies is because it inevitably leads to a very confusing data model for your application. It becomes difficult for anyone to find where data is initialized, where it's updated, and where it's used. Answering the question of "can I modify/delete this code without breaking anything?" is difficult to answer in that kind of a world. And that's the question you should be optimizing for as you code.
One reason we prefer ESModules over global variables is because it allows us to be more explicit about where our values are used, making it much easier to track values and eases the process determining what impact your changes will have on the rest of the application.
Prop drilling at its most basic level is simply explicitly passing values throughout the view of your application. This is great because if you were coming to the Toggle
above and decided you want to refactor the on
state to be an enum, you'd easily be able to track all places it's used by following the code statically (without having to run it) and make that update. The key here is explicitness over implicitness.
What problems can prop drilling cause?
In our contrived example above, there's absolutely no problem. But as an application grows, you may find yourself drilling through many layers of components. It's not normally a big deal when you write it out initially, but after that code has been worked in for a few weeks, things start to get unwieldy for a few use cases:
- Refactor the shape of some data (ie:
{user: {name: 'Joe West'}}
->{user: {firstName: 'Joe', lastName: 'West'}}
) - Over-forwarding props (passing more props than is necessary) due to (re)moving a component that required some props but they're no longer needed.
- Under-forwarding props + abusing
defaultProps
so you're not made aware of missing props (also due to (re)moving a component). - Renaming props halfway through (ie
<Toggle on={this.state.on} />
renders<Switch toggleIsOn={on} />
) making keeping track of that in your brain difficult.
There are various other situations where prop drilling can cause some real pain in the process of refactoring especially.
How can we avoid problems with prop drilling?
One of the things that really aggravates problems with prop drilling is breaking out your render
method into multiple components unnecessarily. You'll be surprised how simple a big render
method can be when you just inline as much as you can. There's no reason to breaking things out prematurely. Wait until you really need to reuse a block before breaking it out. Then you wont need to pass props anyway!
Fun fact, there's nothing technically stopping you from writing your entire application as a single React Component. It can manage the state of your whole application and you'd have one giant render method... I am not advocating this though... Just something to think about :)
Another thing you can can do to mitigate the effects of prop-drilling is avoid using defaultProps
for anything that's a required prop. Using a defaultProp
for something that's actually required for the component to function properly is just hiding important errors and making things fail silently. So only use defaultProps
for things that are not required.
Keep state as close to where it's relevant as possible. If only one section of your app needs some state, then manage that in the least common parent of those components rather than putting it at the highest level of the app. Learn more about state management from my blog post: Application State Management.
Use React's new Context API for things that are truly necessary deep in the react tree. They don't have to be things you need everywhere in the application (you can render a provider anywhere in the app). This can really help avoid some issues with prop drilling. It's been noted that context is kinda taking us back to the days of global variables. The difference is that because of the way the API was designed, you can still statically find the source of the context as well as any consumers with relative ease.
Conclusion
Prop drilling can be a good thing, and it can be a bad thing. Following some good practices as mentioned above, you can use it as a feature to make your application more maintainable. Good luck!
Learn more about React from me:
- egghead.io (beginners) - My Beginner's Guide to React absolutely free on egghead.io.
- egghead.io (advanced) - My Advanced React Component Patterns course available on egghead.io today!
- Frontend Masters - My Advanced React Patterns workshop (currently you can watch the unedited version as a subscriber).
- Workshop.me - I'm giving my Advanced Component Patterns workshop in person in Portland in July!
- Workshop.me - I'm giving my Intro to React workshop in person in Salt Lake City in August!
Things to not miss:
- Animated Timeline - π₯ Create timeline and playback based animations in React, by Nitin Tulswani
- react-perf-devtool - A browser developer tool extension to inspect performance of React components, also by Nitin Tulswani
- MissionBelt: They're just amazing belts.
Some tweets from this last week:
Real talk: What can webpack do that parcel can't? Anyone got a comparison chart or something? β 3 May 2018
I HAVE AN ANNOUNCEMENT! My Advanced #ReactJS
Patterns course is now available on @FrontendMastersβΌοΈ
Over 3 hours of solid examples and exercises for you to simplify your react components and make them more flexible!
Check out the free preview! π frontendmasters.com/courses/advancβ¦ π β 3 May 2018
EmberConf 2018: Say More by @jgwhite youtu.be/iAyRVPSOpy8
I recommend you give this talk a solid watch. It inspired much of what dom-testing-library π is today! β 5 May 2018
I don't think people being disrespectful to other people is ok (or humorous), even if those people seem to "deserve" it. We can disagree and take a stand without being disrespectful. β 7 May 2018
This week's blog post is "Mixing Component Patterns". It's the published version of my newsletter from 2 weeks ago. If you thought it was good, go ahead and give it some claps (πx50) and a retweet:
Special thanks to my sponsor Patreons: Hashnode
P.S. If you like this, make sure to subscribe, follow me on twitter, buy me lunch, support me on patreon, and share this with your friends π
π Hi! Iβm Kent C. Dodds. I work at PayPal as a full stack JavaScript engineer. I represent PayPal on the TC39. Iβm actively involved in the open source community. Iβm an instructor on egghead.io, Frontend Masters, and Workshop.me. Iβm also a Google Developer Expert. Iβm happily married and the father of four kids. I like my family, code, JavaScript, and React.
P.S. Sorry this one was a little late. Normally this will show up in your inbox about 12 hours earlier.