Render Props, Why and How To Use
June 25th, 2020
Render Props is an amazing concept used in React to share code and functionality between components.
With this technique, we have a component that houses some codes and functionalities which other components can partake. The bigger component uses the render prop of the smaller component as a medium to pass some data to the smaller component as well as determine what would be displayed on the UI.
render
is the name used for such props, but it could be named anything, including coffee
. The purpose of the prop is to determine what would be rendered on the UI by the bigger component, so any prop (which has to be a function) that does this work is called a render prop.
Before looking at how to use this awesome feature, let's look at why we'd even need it.
Why Render Props
The most common way of sharing functionalities between two or more components is to create a basic component that receives different types of props to determine what would be displayed. An example of such component is:
import React, { useState } from "react"
//
const Component = (props) => {
const { header, paragraph, increaseCounter } = props
const [counter, setCounter] = useState(0)
const handleInc = setCounter((prevState) => prevState + 1)
return (
<div>
<h1>{header}</h1>
<p>{paragraph}</p>
<button onClick={handleInc}>INC</button>
<p>Current count: ${count}</p>
</div>
)
}
export default Component
This component is very easy to use. Any component that uses this will have to provide a header
, paragraph
and increaseCounter
prop to change the count state. These determine what would be displayed on the UI.
Now imagine we wanted a smaller component to use this component, but with a different header level. We'd need another prop like so:
...
const {header, paragraph, headerLevel, increaseCounter} = props
const H = `h${headerLevel}`
...
return (
<div>
<H>{header}</H>
<p>{paragraph}</p>
...
</div>
)
...
Seems nice so far. What if we wanted three paragraphs? Hmmm...another prop? This breaks usability because the components that would use this component need to study the required props which could be so much.
But don't get me wrong, this method of sharing functionality is not bad. This method is very useful for components which MUST appear in a certain way, or maybe require little props. What I mean is, the method above is very effective if you need to return a div
with four children - 1 <h1>
, 2 <p>
s and 1 <button>
. The component could also be a bit dynamic, but it shouldn't require overload props.
What now is the solution for this? Well, render props is not the first solution for this. The first thing to do is consider how to make this component very usable and simple.
Where does render props come in?
To consider render props, we'd need to understand the common functionalities we want our components to share. For our component above, there's one functionality - using and increasing counter state. Render props technique can come in to help this component manage the state while the consumers determine what would be displayed..
The ideal usage of render props occurs when there are multiple components to share functionality. If there are common functionalities that would be shared across many components and the UI is not guaranteed to be the same, the component used in the code above would not be perfect, because while we consider logic, we need many props to determine UI.
Let's look at a scenario for render props.
It is very common in React applications (or any application) to fetch data from an external service, for example, a server. To achieve this, you may have various useEffect
s and useState
s (using React Hooks) across different components which would be used to get the data and update the state of data during mount.
Looking at this scenario, we can see that ComponentA may fetch all users and display their pictures while ComponentB may fetch a user's details and display the details. ComponentA and ComponentB obviously have different UIs but they do the same thing:
- make fetch request
- update state with response gotten
- use the state on the UI
The state would contain the data gotten and additionally, we could have an isLoading
state (a common practice).
Render props are ideal for such cases. Let's head over to how to use it.
How to use Render Props
Both components share the same logic, so we could manage this logic in one place.
We'd need to have a bigger component which handles the logic that would be shared across other components. Let's call the component FetchComponent
. It would be used like this:
// In the component .js file
import React, { useState } from 'react'
...
const FetchComponent = (props) => {
const {coffee} = props
const [state, setState] = useState({
data: null,
isLoading: false
})
// do whatever with the state
const renderProp = coffee
return (
renderProp(state)
)
}
export default FetchComponent
This is the basic structure of the bigger component. We manage some state (data
and isLoading
), perform some functionalities with the state and render what the consumer component decides to be rendered using the render prop (in our case coffee
, though name not descriptive, but just so you understand that the prop could be called anything).
As you'd notice, this component returns the result (the returned value) of the render prop function which is called with the state argument. This means the consumer component has access to the state.
Our structure now doesn't solve our problem yet. We need to make a fetch request. But to which endpoint? We could receive that as props. Let's modify the component more.
// In the component .js file
import React, { useState, useEffect } from 'react'
import {fetchData} from './utils'
...
const FetchComponent = (props) => {
const {coffee, endPoint} = props
const [state, setState] = useState({
data: null,
isLoading: false
})
const renderProp = coffee
...
useEffect(() => {
const data = fetchData(endPoint)
setState({
data,
isLoading: true
})
}, [])
...
return (
renderProp(state)
)
}
export default FetchComponent
That's it for the main component. Using useEffect, we make a fetch request to the endpoint provided by the consumer component. fetchData
is an assumed function that makes a fetch request. It depends on the method you want to achieve that - could be axios. It would also be asynchronous so you would have to handle it with promise
or async/await
. I didn't bother about that for the simplicity of the article.
When the data is gotten, the state is updated and of course, the consumer component is re-rendered.
Let's head over to our consumer components. For simplicity, we'd consider only two components.
For the first component, which is UsersComponent, we have:
// in the UsersComponent file
import React from 'react'
import FetchComponent from 'path-to-component'
...
const UsersComponent = () => {
return (
<FetchComponent coffee={(state) => {
// do whatever you want with the state
return (
// whatever ui you want
state.isLoading ? (
<p>Loading users....</p>
) : (
<div>
{state.data.map(user => (
{/* show users */}
))}
</div>
)
)
}} />
)
}
export default UsersComponent
For the second component, UserProfile, we have:
// in the UserProfile file
import React from 'react'
import FetchComponent from 'path-to-component'
...
const UserProfile = () =>
return (
<FetchComponent coffee={function(state) {
// do whatever you want with the state
return (
// whatever ui you want
state.isLoading ? (
<p>Loading details....</p>
) : (
<div>
{state.data.map(details => (
{/* show details */}
))}
</div>
)
)
}} />
)
}
export default UsersComponent
From these components, you'd notice how the coffee
prop works. It returns what would be displayed on the UI.
Tip
If you love having elements between opening and closing tag, like <Component>...</Component>
, that's also possible with render props. It's not compulsory for render props components to appear as <Component />
. To achieve this, we can make use of the children
prop. Remember that any prop could be a render prop. Hence we'd have:
In FetchComponent,
...
const renderProp = props.children
return (
renderProp(state)
)
...
In other components,
...
return (
<FetchComponent>
{(state) => {
// ui to render
}}
</FetchComponent>
)
...
Whichever you love, use it.
Things to Note
UsersComponent
andUserProfile
didn't have to look the same. They could use their instance of state however they wanted.FetchComponent
did not care what other components look like. It's majorly concerned with the logic.FetchComponent
is very usable with the very little number of props required.
Wrap up
There are many other scenarios to consider, but this is one which I've found render props helpful.
As I stated earlier, render props shouldn't be the first thing to consider. There's nothing wrong with sharing props to achieve component structure. Think of render props when you are about to share (or already sharing) similar functionalities across several components.
Check out Higher Order Components, a similar technique to render props.
Thanks for reading : )