Using Composition and Render Props instead of Context API
People used to jump to using Redux when they need to pass props more than a few levels deep. After discovering React's new Context API, lots of people started using it instead of Redux when they just need to pass some props down. But if what you are trying to achieve is to just pass down some props without dealing with passing it to every component in between, you don't have to use Redux or Context API.
I use this method often and I wanted to make a blog post about it after seeing Dan Abramov's tweet:
Blogging about React? You’ll help so many people by teaching this pattern. Spread the love! https://t.co/5Kn7ybiiLJ
I will try to explain how to pass our props to deep levels without passing it to every component in between using Composition and Render Props.
It took me a while to learn these topics but when I understood them, I noticed that they are not complicated at all.
In this post, example components will be extremely simple and I will create all components in the same file to make it easier to see what's going on.
I created a React app on codesandbox.io and you can see the final version in the link below:
For this example, I created four nested components namely Main, First, Second and Third. Our final component structure should look like this:
Let's imagine Main has a state which we want to pass to Third. With prop drilling, we can keep passing our prop down until we reach the component that we are going to use it.
If we are using prop drilling, our components will look like this:
So the Main component has the state and we want to pass it to the Third component. Since we only have three components, this solution is completely fine. However, things get complicated if we need to go much deeper than this. But for simplicity, I won't add any more components.
As expected, if I mount my component to the app, it should work. Let's test it:
So far there is nothing new. But let's try something else now.
I created another file, Composition.js and copied the code from WithPropDrilling.js. This time, I want to reshape the Main component. Let's try something like this:
Now, it looks like we found a way to pass the prop directly to the Third component. Let's do a test drive:
Looks like we weren’t successful at passing the prop to the Third component. Could the structure we provided for the Main component be incorrect? Let’s add another element and test if we can see it:
It's looking like our app doesn't care about the structure we created in the Main component. Actually, we passed children elements to our components in the Main component but we never used them. Instead, we statically used our component names as children. So components have nothing to do with the children we passed.
Right now, we are explicitly declaring which components will be children to the First and Second components. What would happen if the Main component was deciding what other components will render as children? Will this change anything?
Since we already created the component structure in our Main component, there is no need to use component names statically in our children components. Instead, we can use props.children. Let's make this little change:
Now the components First and Second doesn't know which other components are their children. We took Now the components First and Second doesn’t know which other components are their children. We took this responsibility from our Second and Third components. We created our structure in the Main component and we are deciding what our Second and Third components will have as children in the Main component.
Let’s check again after the changes:
We can now see the text passed as a prop along with the h2 element which is the sibling element of our Third component.
So far we could manage to pass components down without using Prop Drilling, Context API or Redux. But there is one other way to create a similar behavior. It’s called Render Props.
First I recommend you to read the official docs:
Render Props are just props carrying a function which returns JSX. In simpler terms, according to the official docs, " a render prop is a function prop that a component uses to know what to render."
The name "render" is just for naming convention, you can give any name to this prop but I will stick to using render.
I created a copy of Composition.js and renamed to WithRenderProps.js. With a few small differences, we can get the same functionality again.
This time in the Main component, instead of placing Second and Third components as a descent of the First component, we will create a prop named render. This prop will be an arrow function which will return what to render in the component. Now it is just like using children, but we are returning our children components in our render prop, not placing them directly under component.
And since component First doesn’t have any children now, we need to make a change there too.
So, why might we need this approach? What kind of extra benefits render props could provide us? The answer is simple, passing parameters. To show an example, let's create a random number in component First. Then pass this number as a parameter to the render function.
Let's add a place to show this number in the Main component.
Now we should see a random number between 0–9 on every refresh. Actually refresh is not necessary, a state change will trigger components to re-render. We could also use the forceUpdate method on the component Main but I want to give more visible examples. To see this let's add 'counter' to our state, 'increaseCounter' method and elements to display our values.
Now every time when we click on the button it will increase the counter. This change will cause a re-render and we will see a new random number.
I want to add one more example about Render Props. In the Main component, I add two buttons as children. I want them to be rendered under the Third component but I want to decide their background color in the First component. I'm adding two more parameters to the function invocation inside the component First.
We should edit our Render Prop to take two more parameters and use them as inline styles of buttons
Almost there! The only thing missing is we didn't tell our Third component to render the children. After adding it we are good to go.
I added borders and padding to the components to make it easier to understand what is going on. Also Added an extra div to the Main component to visualize component structure better
Our state which holds the text is in the Main component. First, we passed it with Composition directly to component Third without Component Drilling or Context API. Then we changed how our component First works. We created Render Prop, a function which will return us what to render inside the component. We invoked this function and it worked in a similar way with Composition. Then we added a parameter to this function and we used this parameter in our render prop. This way we passed the number from component First to component under Second without using props. After that, we added two more parameters to our function invocation, 'red' and 'white'. And the same way, we have them in our render props as parameters. Now we have color1 and color2 as parameters in our render prop. I added two buttons as children of component Third. Using inline styles, I added 'backgroundColor' style and as the value, they got color1 and color2. Now our component Third renders two buttons, we placed this buttons in the Main component and the First component provides the colors we need. And we did all of them without using Prop Drilling, Context API, Redux or any other state management system.
You can subscribe to my mail list and stay up to date with my posts.