“render props” pattern

It’s great to observe how developers are constantly discovering new patterns of component composition in React. Some pattern which once you considered as an anti-pattern suddenly gains a name and devs start using it. On the other hand, some well established patterns become obsolete because there’re better ways how to solve the problem.

I started using so called render props in jsLingui i18n library to customize rendering of translations. I didn’t know this pattern is called “render props”, but it quickly get popular in React ecosystem. Few days ago I got a question about withI18n high-order component whether there is a “render prop” variant and naturally I pointed user to render prop of Trans component. His response was interesting:

this is already a kind of render prop, albeit an interesting variation which i have not seen, yet

I thought: What? It’s a prop, it’s called render and it’s used to customize rendering. What else could be more “render prop”? So I start googling and found an article in official React docs about render props.

I don’t want to blame anyone because naming things is one of the hardest thing in programming. Instead I would like to write down my own point of view on “render props” pattern.

“render props” as defined by React docs

The term “render props” is usually used for following pattern:

<DataProvider>

{(data) => data.map(item => <li key={item.id}>{item.value}</li>)}

</DataProvider>

We have a data provider (DataProvider) which might encapsule data fetching, complex behavior, but delegates rendering to children. There’s a single child, a function which takes a data as a parameter.

The naive implementation might be following:

function DataProvider({ children }) {

const data = /* fetch data somehow: state, derive from props, … */

return children(data)

}

The term “render prop” originates from similar use case, where prop render is used instead of children:

<DataProvider render={

(data) => data.map(item => <li key={item.id}>{item.value}</li>)

} />

After all, children are just special case for prop.

Question is, why this pattern is called “render prop”?

The important thing isn’t the prop named render , but the fact that the value of this prop is a function.

And not just any function, but an anonymous function defined inside render method. Why not call it lambda component?

Let’s go down the rabbit hole.

Replacing lambda components with components and elements

Imagine, that the function would accept an object as a first (and only) argument:

<DataProvider>

{({ data }) => data.map(i => <li key={i.id}>{i.value}</li>)}

</DataProvider>

Now the lambda component looks very much like function component:

const DataRenderer = ({ data }) =>

data.map(i => <li key={i.id}>{i.value}</li>) <DataProvider>

<DataRenderer />

</DataProvider>

Make sense, right? Except it won’t work, because our implementation of DataProvider expects children as a function but now we’re passing not even a function component, but an element.

Note: component is DataRenderer , element is <DataRenderer /> . <DataRenderer /> in JSX is transformed to React.createElement(DataRenderer) .

Let’s update our DataProvider :

function DataProvider({ children }) {

const data = /* fetch data somehow: state, derive from props, … */

const props = { data } return React.isValidElement(children)

? React.cloneElement(children, props) // element as children

: React.createElement(children, props) // component as children

}

Now DataProvider supports both patterns. Classic composition, but inner component gets injected data from parent:

const DataRenderer = ({ data }) =>

data.map(i => <li key={i.id}>{i.value}</li>) <DataProvider>

<DataRenderer />

</DataProvider>

And “render prop” pattern, where we pass a lambda component as a child:

<DataProvider>

{({ data }) => data.map(i => <li key={i.id}>{i.value}</li>)}

</DataProvider>

So, we might have a “render prop” behavior, but without the lambda component and the question remains: what’s the difference? The scope!

Passing data through scope

We could extract our function in previous example to function component only because the function is “pure”: it doesn’t use data from outside scope, even it has access to it. Imagine a different component:

function DataPage(props) {

const computedProperty = compute(props)



return (

<DataProvider>

{({ data }) => (

<div>

{computedProperty}

{data.map(i => <li key={i.id}>{i.value}</li>)}

</div>

)}

</DataProvider>

)

}

Lambda component uses data from parent scope! If we declare it outside the DataPage scope, we need to pass required data explicitly:

const DataRenderer = {({ data, computedProperty }) => (

<div>

{computedProperty}

{data.map(i => <li key={i.id}>{i.value}</li>)}

</div>

)} function DataPage(props) {

const computedProperty = compute(props)



return (

<DataProvider>

<DataRenderer computedProperty={computedProperty} />

</DataProvider>

)

}

And we’re back to good old component composition. It’s not obvious that DataProvider actually injects data prop to DataRenderer , but it’s the same pattern, same behavior, only without lambda components.

It seems we have three ways how to pass data in React:

through props

through context

through scope

All of them have props and cons and depend on specific use case.

Conclusion

It took me several hours to write this short article as I was completely lost in different patterns. I also discovered few patterns which I didn’t know that exists in React. Anyway, this is my point of view on “render props” and why I find the name confusing.

What’s your opinion about name lambda components?

Last question remains: how to name DataProvider , the component which consumes either component or element and injects a data to it? Is it a container? Provider? “render prop” component? Personally I’m in favor of container, because it usually defines a behavior or business logic, rather than presentation logic.

“render prop” pattern isn’t an ultimate replacement for HOC, because you can’t use data from container in lifecycle methods. It’s still useful pattern in some cases and definitely one which should be in arsenal of every React developer.