Render Props and Higher Order Components

July 22, 2018 0 Comments

Render Props and Higher Order Components

 

 


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:

I would appreciate your ❤️ & 🦄 on my entry-post


Tag cloud