Cover image by Graffiti Picture Taker, on Flickr
Last week I entered the first ever dev.to contest and submitted a serverless multiplayer clicker game.
It would be awesome to get your โค๏ธ & ๐ฆ on my entry-post
I'd also like to give you some know-how in return.
If you're a React developer and followed the ecosystem for a few years, you probably noticed the advent of render props (RP), or function as children, lately.
All the time people were telling you about higher order components (HoC) and now this?
Well I was confused too, but if you take React programming practices into account, you'll see that RPs make totally sense.
What
Render props are simply props that will be used in a render call somehow. They take a function that needs to return an element. This function also gets some dynamic data via its arguments, these can be used by the returned elements.
They are the dependency injection alternative to HoC.
Why
Every time you create an element from your RP based component, you can pass different elements into its RP. A HoC will wrap your component at definition time and not at render time.
In the last years it became common in React coding practice to use dependency injection to create nested elements, RPs are a natural extension of that principle.
For example, you wouldn't define a List
component like this:
const List = props => <ul>{props.data.map(i => <ListItem text={i}/>)}</ul>;
const ListItem = props => <li>{props.text}</li>;
// usage
<List data={["hello", "world"]}/>
Because now your List
needs to know about the data
and which ListItem
it needs to render.
Instead you would define it like this:
const List = props => <ul>{props.children}</ul>;
const ListItem = props => <li>{props.text}</li>;
// usage
<List>
{data.map(i => <ListItem text={i}/>)}
</List>
Because now you can inject the data
and child components into the List
and it just has to render it. You could, for example throw in another ActiveListItem
the List
doesn't need to know anything about.
Components with RPs play really nicely with this. Just imagine, your List
would simply render all its children
and pass them some data it gathered.
Higher Order Fetch
HoC are another way to do this, but the idea behind them is to create a wrapped
component you can use everywhere that has some extra abbilities.
A fetch as HoC could look like this
const wrapWithFetch = Wrapped => class Fetch extends React.Component {
state = { result: null };
componentDidMount() {
fetch(this.props.url)
.then(r => r.json())
.then(result => this.setState({result}))
}
render() {
const {result} = this.state;
return result? <Wrapped data={result}/> : null;
}
}
// Stateless component that displays text
const Text = ({data})=> <p>{data.name}</p>;
// Wrappted text that gets data
const FetchText = wrapWithFetch(Text);
// Usage
<FetchText url="/user/123"/>
Render Prop Fetch
The RP version could look like this:
class Fetch extends React.Component {
state = { result: null };
componentDidMount() {
fetch(this.props.url)
.then(r => r.json())
.then(result => this.setState({result}))
}
render() {
const {result} = this.state;
return result? this.props.render(result) : null;
}
}
// usage
<Fetch url="/user/123" render={user => <p>{user.name}</p>}/>
When it's mounted it will fetch some data and pass it to the RP.
Since children
are props, you could also use them instead of a custom prop.
<Fetch url="/user/123">{user =>
<p>{user.name}</p>
}</Fetch>
Which would lead to a Fetch
component that looks like that:
class Fetch extends React.Component {
state = { result: null };
componentDidMount() {
fetch(this.props.url)
.then(r => r.json())
.then(result => this.setState({result}))
}
render() {
const {result} = this.state;
return result? this.props.children(result) : null;
}
}
As you can imagine, now you can simply wrap any children into a function that will receive data from the server and only be rendered when the data is available.
Conclusion
Render Props can be used to add even more dependency injection into your app, making it much more flexible to change.
You can add new elements into the RP to simply change what is displayed, for example change tables to Graphs etc.
But you can also change the wrapping RP component so the children will now receive data from a different source, but you wouldn't have to change the children, because you could map the data from the RP arguments to the right child-props on-the-fly.
Contest
Also, if you liked this post:
Top comments (4)
Great article K, really contrasts HOC and render props.
Just a minor concern, I am not sure whether the first example you posted qualifies as a render props.
As you said a render prop is a function which the child component invokes to figure out how to render dynamically. However in the example above you are not passing any function to child component, instead you are passing a simple array of react elements.
Cheers ๐ฅ
Thanks!
This wasn't an example of a render prop but of general dependency injection.
I tried to show how DI leads to RP. :)
"you said a render prop is a function which the child component invokes"
No no, the children will be
returned
by this function, the parent invokes it, because the parent gets it passed via the render prop. :)Gotcha! Thanks for clarification.
Awesome post bro. I loved the way you dissected the topic. But is it a bad practice to use HOC instead of RP or can you use either based on the problem you are trying to solve? Sometimes I have component eg a page that is set to receive certain props when wrapped with a HOC so I just need to declare the page wrapped with the HOC as a component to be displayed for a certain route in my application. With this kind of example I don't think I need render props or what do you think? Would appreciate your view on this.