Render Props in React

K
Bytesized Code
Published in
4 min readJun 2, 2018

What

First, what is a render prop (RP)?

A RP is simply a prop that takes a function which returns elements that will be used in render().

You can pass an element directly into a prop and use it in render() which would make the whole thing a RP by name, but generally, when people speak about RPs, they mean the first definition.

Also, since the children of a component are also a prop, you can use them instead of other/named props, which is also called function as children.

const C = props => props.renderProp();
// or
const C = props => props.children();

Why

To get the why of RPs, we first have to do a step back.

In the last years it became common practice in React to use dependency injection to create nested elements, RPs are a natural extension of that principle.

Dependency Injection (DI): The basic idea is, you remove hard-coded identifier from your code. For example, a function could use another function to do something, but it could also get a function reference passed into it that does something.

Here an example, a function that creates a string with a random value. The first version uses Math.random() directly, the second one doesn’t know anything about it and could use a completly different source for its random value.

// Non-DI
const f = () => "The value is: " + Math.random();
// Usage
f();
// DI
const f = g => "The value is: " + g();
// Usage
f(Math.random);

How does this translate to React?

A common problem in graphical user interfaces are lists of multiple items. You often have a bunch of items, list them and suddendly you need an item of a different type in that same list.

If you implement it naively you could end up with something like that:

const List = props => (
<ul>
{props.data.map(i => <ListItem text={i}/>)}
</ul>
);
const ListItem = props => <li>{props.text}</li>;
// Usage
<List data={["Hello", "World"]}/>

But now your List needs to know about ListItem so when you want to add something like HighlightListItem you would have to change the List. While this isn’t a big problem in such a small example, it could get more complicated with others, like when you don’t implemented the List and now need to ask some other developer to do it for you.

The DI way to do it could look like that:

const List = props => <ul>{props.children}</ul>;
const ListItem = props => <li>{props.text}</li>;
const HeaderListItem = props => (
<li style={{background: "red"}}>
{props.text}
</li>
);
// Usage
<List>
<HeaderListItem text="Header!"/>
{data.map(i => <ListItem text={i}/>)}
</List>

As you can see, the List doesn’t know anything about ListItem or HeaderListItem it just renders its children . Which requires a bit more typing at the call-site, but makes the component much more flexible and less opinionated.

Now that we understand how DI leads to more flexible code, what does this has to do with RPs?

RPs are like a natural extension of that principle to logic. While the DI example I showed you worked rather nice for flexible element composition, RPs allow you to use this to encapsule other kind of logic, so it can be used with different components.

How

Like I already told you, RPs are simply props of a component you can pass functions into. These functions have to return elements, which will be used in that components rendering.

A simple example could be a Random component, that delivers a random number to its RP.

const Random = props => props.render(Math.random());
const Text = props => <p>{props.children}</p>;
// Usage
<Random render={number => <Text>{number}</Text>}/>

In this example Math.random() is still hard-coded (non-DI, for brevity sake), but the Text element is injected and gets passed the random value via the function it’s wrapped with. The wrapping function prevents the creation of the Text element till it’s called by Random and allows some logic to happen before it’s called.

Now every component can be wrapped with Random and since the values are passed into the function as arguments and not directly into a specific prop, you can decide at call time what argument you want to pass into which prop of the injected component.

Comparison with Higher Order Components

For a long time higher order components (HoC) were the way to share logic in React, so why did this change?

First, lets see how the Random example would look like with a HoC.

const withRandom = (Wrapped, targetProp) =>
class Random extends React.Component {
render() {
return <Wrapped {...{[targetProp]: Math.random()}} />;
}
};
const Text = props => <p>{props.children}</p>;
// Usage
const RandomText = withRandom(Text, "children");
<RandomText/>

As you can see, this creates a wrapped version of Text at definition time and not at render time. While this is a form composition, which is favored over inheritence, it forces you to create a new component every time you want to wrap something, also it’s less flexible in terms of props, since they would also have to be defined at wrapping time.

With RPs wrapping and usage time are the same, so you can do all in one go.

Conclusion

RPs can be used to add flexibility into your app, making it much more resilient to change.

You can add new elements into the RP to simply change what is displayed, for example change tables to Graphs etc.

You can also change the wrapping RP component to let the children rceive 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.

This article was originally published on dev.to.

Byteconf React is a free React/JavaScript conference, streamed on Twitch. Join us on August 31, 2018, to hear from the best React developers and teachers from around the world. Join our mailing list and follow us on Twitter to stay up to date!

--

--