In this post, I’ll show you strategies to avoid creating a prop drilling mess in your React code. I will also provide some tips for better organizing and maintaining your components.
If you find yourself drilling props often, that can be a signal to reconsider your application’s data flow or decision tree and an opportunity to refactor.
Lift state up
Lifting the state up in the component tree can help minimize prop drilling.
You remove the need to pass more props down to child components by lifting the state that depends on those props to the parent. It also makes the state more flexible and easier to share between multiple child components.
Say you have a list of usernames that you want to display. Each username item decides whether it should render its content using the props you pass:
Should it really be up to
UserNameListItem to decide whether to render itself? Instead of passing multiple props to
UsernameListItem, you can decide whether to render it from within the
Now we no longer need to worry about passing the
selectedUserId prop to
This approach also enforces a good separation of concerns: The child only cares about rendering the username, and the parent decides when to render the child.
Making props more flexible
Another way to avoid prop drilling is to make your props more flexible. I often see this pattern with React components: the component has a long list of props. Most of those props are booleans that dictate whether or not to display a specific parent of the UI for one reason or another.
This problem doesn’t appear immediately, but when the number of different ways to display the UI starts to grow, it can quickly become unmanageable. When you see a long list of props like this, it’s a good sign that component is due for some refactoring.
What if instead of providing
canDelete, we provide an array of actions the user can take?
This way, we can add, remove, or change the order of actions without affecting the props. And the child component doesn’t need to know about those actions, it only needs to render the ones it receives.
Context API or global management solutions
There are a lot of great online posts on this topic, so we won’t go into too much detail here.
But briefly, Context API (or a global management solution like Redux) is considered a to-go solution for solving the prop drilling problem.
By putting your data in a global store, you can access it from anywhere in your application without having to pass props down the component tree.
Notice how we don’t need to pass the
theme prop through all the components in order for the
ThemedButton to have access to it.
This pattern can be helpful when you have data used in multiple places throughout your application.
Cons of using the Context API
While using a global state is often a valid solution to avoid prop drilling, it’s important to note that you don’t want to use a global store for everything. Only the data that is truly global (or necessary) should be put in there. Otherwise, you’ll end up with a bloated store and an application that is difficult to reason about.
Also, your components become reliant on the context, which makes them harder to test and reuse.
There is also a performance cost associated with using Context, so you want to make sure not to use it for data that’s updated often, like users typing their input. That will cause the whole component tree to re-render on each keystroke, which will have a visible impact on the performance.
Component composition is a way of avoiding prop drilling using proven principles that will also make your code easier to read and maintain.
With component composition, you build complex components on top of simpler ones.
There are two main patterns in component composition: Container pattern and specialized components.
The container pattern is a variation of the render prop pattern, which you might have seen before. The container is a wrapper component with distinct functionality and can be wrapped around any other component.
In this case, we have a
GateComponentcontainer that uses a
useGetUserSession hook to get the authentication state from our application. The
GateComponent will only render its children if the user is authenticated.
With this pattern, you don’t need to declare your child component inside your container. Instead, you can simply wrap it with the container wrapper, thus avoiding at least one level of prop drilling.
Specialized components are generic components with the value of their props predefined ahead of time. In other words, they are generic components tailored for a specific use case.
For example, say we have a generic
We want to build a specialized
UserListItem component that will always display the user’s name and avatar. To do that, We can create a new component and use our generic
ListItem in it.
Now we have a reusable component that will display the name and avatar. We don’t need to worry about passing the props to ListItem ourselves, the
UserListItem component will take care of it. All we need to do is provide the
Check my other post to learn more about component composition .
Compound components are a step further from the component composition. It’s a technique for building complex components using simple building blocks where each of those blocks shares the implicit state of the parent component.
You have probably seen this technique when working with component libraries that provide components like Accordion, Tabs, or Carousel.
Compound components are powerful because they allow you to build complex pieces of UI in a declarative way without doing much prop drilling.
If you would like to learn more, check out my post on building compound components.
And that’s it for this post. We covered different techniques and approaches for avoiding prop drilling and their pros and cons.
If you’d like to get more web development, React and TypeScript tips consider
following me on Twitter,
where I share things as I learn them.