Using `Render props` to compose react components

Dong Chen
4 min readMay 21, 2018
Photo by rawpixel on Unsplash

Ideally, building an app should be like building lego. Each component is a piece of functionality and we just compose them together. However, it is actually not that straightforward as we have more things to take care of. For example, we must implement the API exposed by each component, including the event handlers, and props needed. We also probably need to maintain an internal state, update the state in event handlers, and map state to corresponding component props. How should we share these codes when we try to compose components?

The two pages we are going to implement share the same components and logic, but with slightly different layout

The problem occurred to me when we ran into a need for A/B testing a page layout. The two pages look very similar and include the same components, but with different layout and styles. The page one code looks like this:

On a high level, the code includes

  • how it looks: individual components, and their layout
  • how it works: event handlers, and props for individual components. And the event handlers are likely to change the state, which in turn update the props for individual components.

How should we compose Page Two?

We could just duplicate the code in Page One, and do whatever we want for Page Two. This is, however, bad for maintainability; you have doubled code size and thus doubled your maintaining efforts 😱

Or we can tweak Page One and add some if else:

This is a bit better as we do reuse all the event handlers. However it is not scalable; what if you have another page design? And all the if else over the code makes it harder to follow and maintain.

Solution

We have render props pattern to help! Basically we can have children as function, and pass our component rendering function as parameters to children.

We first define a base page component. In this component, we define all the event handlers and components, and pass them down to children (the consumer of the base page component)

In page one component, we can use the functions/props passed down from base page, and do things we want

Note we are reusing component A and B defined in base page, but we can pass our own props.

Tip: we wrap component in a container. This is only for layout purpose. To make component better reusable, it’s better not to include any layout css (e.g. margin, padding); instead, leave it to user of the component.

Page Two component can do the same thing as Page One, but has its own props for components. Besides, Page Two has absolute control over the layout without affecting Page One. It can put component B above component A, hide component B, or add an extra component. Pretty neat!

A bit further

What if we want to A/B test another version of component A, say A1? Well, we can add component A1 in base page and pass it down to its children, just like our existing component A and B. In this case, we can share event handlers for component A and A1.

Be more flexible

But users could want many other versions of component A, and we can’t implement all of them. We could be more flexible by allowing user to render their own version of component A, say A2. But it is likely that the event handler remains the same, so we can pass that down.

In Page One, we can just use the event handlers passed down from base page, and have our own component. Note though that it comes at the cost of writing more code to implement our own component.

Further, we can predefine and share other component props than event handlers. For example, we can predefine accessibility attributes. Kent C. Dodds calls it prop collection and getters. In this way we share the component props but allows for the flexibility to override.

And in page one, we have

Page one can pass its own props to overwrite the props defined in base page.

Conclusion

Looking back, this is a pretty straightforward way to share code. We define utilities that we think can be reusable in the base (parent) component, and pass these utilities as parameters to children functions.

We need to decide what to reuse and balance the benefits and costs then. We can define both “how it looks” and “how it works” by wrapping up components with their event handlers and pass them to children, so that users of the base component can render them directly in the desired layout. This comes at the benefit of writing least code for users, but at a cost of being less flexible in rendering individual components.

We can also only define “how it works” and leave “how it looks” to users by passing down only prop getters we think necessary. Users can implement their own UI component and feed it with props from base component (yet still with capability to overwrite). This comes at best flexibility, but users need to implement their own UI.

So it depends on your use case to decide how much base component should implement for your user. Or you can even mix the two patterns, and implement both a predefined component and prop getters, and pass down all. Users then can pick what they want. If they don’t want to bother with UI implementation, just use the predefined one; or they can take the props and implement their own UI. However, this makes the size of base component larger.

--

--

Dong Chen

Web engineer @robinhood; PhD in Human-Computer Interaction