Passing Elements as Props in React
The middle ground between a props explosion and render props
In this story we are going to see a simple technique that allows you to write friendly customizable components with simple APIs, just by using the basic React building blocks: the element.
In my experience, a seasoned React programmer uses this technique naturally, but a newcomer struggles to grasp it. If you are the former, I won’t waste your time. This story is for the latter.
props explosion? Wut?
With props explosion (patent pending) I mean having props to customize the smaller components that build your component or having nested props. Let’s see this with an example.
Suppose you have an
Alert component with a
What can you do to make the inner
Button customizable? If you are a React newcomer and read the docs you probably think the correct way of doing this is by passing props.
Fair enough, but what happens if we are dealing with a
Confirm component with two buttons? You need to duplicate props and have some prefixing to avoid clashing.
As you can imagine this will only get worse as your component grows. This is what I meant with props explosion.
Tidying up. Nesting props
One way to tidy the code a little is by nesting props: one prop for each inner component with the necessary keys.
This fixes the clashing, but things are getting complex. What happens if I want to override just one nested prop? To support that we need to manually merge the props with the defaults. And, what happens if the
Button also contains an
Icon? Should you use a pair of new props (
rejectButtonIcon)? Should you nest them inside the existing ones (
acceptButton.icon)? Your prop types are going to be so much fun to read. By now, you should get my point.
The all or nothing approach: render props
Maybe you have read that a way to make your components really customizable, instead of falling into a prop hell, is using a render prop. Let’s try that. We’ll code three new versions of the
First, one render prop per button:
Then, one render prop for all the buttons:
And, finally, a render prop for all the component:
The last one is kind of extreme.
Confirm is a controlled component and it doesn’t make much sense. Imagine that
Confirm is an uncontrolled component with its own state and behaviour and it will make more sense.
Render props are as powerful as it can get but they also come with their own nuances. They’re kind of an all or nothing approach. They let you change how you render the component but it’s not easy to reuse the “defaults”. It depends on what your render prop renders. In the previous examples we saw three levels, each allowing more flexibility but less reusability of the “defaults”.
However, this approach may seem overkill when we just want to customize the text of the
Confirm buttons. There must be something we can do to find a middle ground.
The middle ground
As you guessed from the title of this story, there is a middle ground: passing elements as props.
This API is much more natural to just change the “clickable” parts of
Confirm. No props explosion hell. No forcing a render prop API onto the user. This is one of that instances where
cloneElement is the right tool.
Note: if you wonder why we are always passing the handlers when, in some instances, it would make sense to just use them directly, you are right. In this case it’s not mandatory. For example, you can do the following and not clone the
Confirm need to wrap or do something with the handlers or is it’s just passing down the props? Do you need to overwrite or merge the
className? Choose your API wisely.
Why the struggle?
I believe it’s JSX. Specifically, newcomers approach to JSX.
JSX has always been the most WTF thing about React and it has been thoroughly debated. It’s a very powerful tool but to the eye of an untrained developer can seem magical: “it’s HTML on your JS” or “it’s how you write your component render function”. It’s much less and much more.