An Overview of Render Props in React
Using render props in React is a technique for efficiently re-using code. According to the React documentation, “a component with a render prop takes a function that returns a React element and calls it instead of implementing its own render logic.” To understand what that means, let’s take a look at the render props pattern and then apply it to a couple of light examples.
The render props pattern
In working with render props, you pass a render function to a component that, in turn, returns a React element. This render function is defined by another component, and the receiving component shares what is passed through the render function.
This is what this looks like:
class BaseComponent extends Component {
render() {
return <Fragment>{this.props.render()}</Fragment>;
}
}
Imagine, if you will, that our App
is a gift box where App
itself is the bow on top. If the box is the component we are creating and we open it, we’ll expose the props, states, functions and methods needed to make the component work once it’s called by render()
.
The render function of a component normally has all the JSX and such that form the DOM for that component. Instead, this component has a render function, this.props.render()
, that will display a component that gets passed in via props.
Example: Creating a counter
See the Pen React Render Props by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.
Let’s make a simple counter example that increases and decreases a value depending on the button that is clicked.
First, we start by creating a component that will be used to wrap the initial state, methods and rendering. Creatively, we’ll call this Wrapper
:
class Wrapper extends Component {
state = {
count: 0
};
// Increase count
increment = () => {
const { count } = this.state;
return this.setState({ count: count + 1 });
};
// Decrease count
decrement = () => {
const { count } = this.state;
return this.setState({ count: count - 1 });
};
render() {
const { count } = this.state;
return (
<div>
{this.props.render({
increment: this.increment,
decrement: this.decrement,
count: count
})}
</div>
);
}
}
In the Wrapper
component, we specify the methods and state what gets exposed to the wrapped component. For this example, we need the increment
and decrement
methods. We have our default count
set as 0
. The logic is to either increment or decrement count
depending on the method that is triggered, starting with a zero value.
If you take a look at the return()
method, you’ll see that we are making use of this.props.render()
. It is through this function that we pass methods and state from the Wrapper
component so that the component that is being wrapped by it will make use of it.
To use it for our App
component, the component will look like this:
class App extends React.Component {
render() {
return (
<Wrapper
render={({ increment, decrement, count }) => (
<div>
<div>
<h3>Render Props Counter</h3>
</div>
<div>
<p>{count}</p>
<button onClick={() => increment()}>Increment</button>
<button onClick={() => decrement()}>Decrement</button>
</div>
</div>
)}
/>
);
}
}
Example: Creating a data list
The gain lies in the reusable power of render props, let’s create a component that can be used to handle a list of data which is obtainable from an API.
See the Pen React Render Props 2 by Kingsley Silas Chijioke (@kinsomicrote) on CodePen.
What do we want from the wrapper component this time? We want to pass the source link for the data we want to render to it, then make a GET
request to obtain the data. When the data is obtained we then set it as the new state of the component and render it for display.
class Wrapper extends React.Component {
state = {
isLoading: true,
error: null,
list: []
};
fetchData() {
axios.get(this.props.link)
.then((response) => {
this.setState({
list: response.data,
isLoading: false
});
})
.catch(error => this.setState({ error, isLoading: false }));
}
componentDidMount() {
this.setState({ isLoading: true }, this.fetchData);
}
render() {
return this.props.render(this.state);
}
}
The data link will be passed as props to the Wrapper
component. When we get the response from the server, we update list
using what is returned from the server. The request is made to the server after the component mounts.
Here is how the Wrapper
gets used:
class App extends React.Component {
render() {
return (
<Wrapper
link="https://jsonplaceholder.typicode.com/users"
render={({ list, isLoading, error }) => (
<div>
<h2>Random Users</h2>
{error ? <p>{error.message}</p> : null}
{isLoading ? (
<h2>Loading...</h2>
) : (
<ul>{list.map(user => <li key={user.id}>{user.name}</li>)}</ul>
)}
</div>
)}
/>
);
}
}
You can see that we pass the link as a prop, then we use ES6 de-structuring to get the state of the Wrapper
component which is then rendered. The first time the component loads, we display loading text, which is replaced by the list of items once we get a response and data from the server.
The App
component here is a class component since it does not manage state. We can transform it into a functional stateless component.
const App = () => {
return (
<Wrapper
link="https://jsonplaceholder.typicode.com/users"
render={({ list, isLoading, error }) => (
<div>
<h2>Random Users</h2>
{error ? <p>{error.message}</p> : null}
{isLoading ? (
<h2>Loading...</h2>
) : (
<ul>{list.map(user => <li key={user.id}>{user.name}</li>)}</ul>
)}
</div>
)}
/>
);
}
That’s a wrap!
People often compare render props with higher-order components. If you want to go down that path, I suggest you check out this post as well as this insightful talk on the topic by Michael Jackson.
thanks for the post.
it seems to me this technique is only relevant when there might be multiple presentational components using the same logic of the wrapper. if there is only one presentational compoent likely to use this logic it doesn’t make sense to use the technique.
make sense?
can you suggest other usecases for this?
thanks
“Imagine, if you will, that our App is a gift box where App itself is the bow on top.” So what is the App?