Implicitly passing props with React cloneElement

Published on
6 min read
Related tags

Leverage the React.cloneElement API to pass certain props implicitly.

This article was originally posted on Medium back in 2019, and I've ported it here for historical purposes. Read on medium.com.

While working on a fun side project recently, I ran into an interesting situation where I needed to pass a prop down to multiple children that would help determine the styling of those children. There was a slight caveat — I did not want to do this explicitly, but rather implicitly in a specific context.

Let's explore this!

Note: there are a couple of references to styled-components throughout this article, and it's assumed you have a basic understanding of the library.

The component that we're going to dissect is FloatingLabel, which is essentially a stylistic wrapper that renders an input and a label in a “floating” style.

If you're not familiar with what this is — the input and label are styled in such a way that if the input is focused, the label shrinks into the top left corner to make room for any text (such as placeholder text or text typed by the user). The effect is pretty nice — you can have labels (something you should be using anyway for accessibility) but they don't have to clutter the UI, and can sit inside their respective inputs.

I went back and forth on structuring the FloatingLabel component, and initially was leaning towards a more traditional API with standard passing of props. The component was more generalized and was simply called Input, which rendered what it needed (an input and a label, wrapped in a div).


_10
<Input
_10
name="blah"
_10
id="blah"
_10
labelName="Cool Label"
_10
value={blah}
_10
onChange={this.handleChange}
_10
placeholder="Lol"
_10
/>

However, after reading this awesome post that advocates for using children as props, I decided to restructure my API to be more explicit.

In the above code we are passing some props to Input and it's unclear what's going on. For example, what does Input even render? At first glance you might think it only renders an HTML input, however it's also rendering a label using the labelName prop. And when looking at props, questions arise such as... is the id prop for the input or the label? Is the placeholder prop for the label text, or the input placeholder?

So after playing around with multiple configurations, I ended up refactoring my component API to resemble this:


_10
<FloatingLabel>
_10
<Input
_10
id="blah"
_10
name="blah"
_10
value={firstInput}
_10
onChange={this.handleChange}
_10
placeholder="Hi"
_10
/>
_10
<Label htmlFor="blah">Float On</Label>
_10
</FloatingLabel>

FloatingLabel is a stylistic wrapper of sorts (rendering a div that is positioned relative, something that is necessary for the effect) which wraps an input and a label using this.props.children. Notice how everything is now very explicit - you know exactly which HTML elements are used and which props are for which components.

The interesting problem I ran into next was how to style Input and Label to “float” in this particular context only, since we want to be able to reuse them without having to always float.

Technically, we could pass a prop signaling for it to float, something like:


_13
<FloatingLabel>
_13
<Input
_13
id="blah"
_13
name="blah"
_13
value={firstInput}
_13
onChange={this.handleChange}
_13
placeholder="Hi"
_13
float="true"
_13
/>
_13
<Label htmlFor="blah" float="true">
_13
Float On
_13
</Label>
_13
</FloatingLabel>

However, declaring it twice felt really cumbersome to me, and it seemed a little unnecessary because we know that if we're looking to render a FloatingLabel then we definitely want the Input and Label to “float”... we shouldn't have to explicitly define this, it should just happen.

Luckily, React provides some handy utilities to deal with situations like this, where the children props data structure is unknown and we need to dynamically pass props around.

Using the React.Children.map utility, we can map over each child and invoke a function on each. We can then use React.cloneElement to assign the float prop to each child, which we can then leverage for styling. The API for FloatingLabel ultimately ended up looking like this, where children is getting wrapped with the new props:


_15
const FloatingLabel = props => {
_15
const { children } = props;
_15
const childrenWithProps = React.Children.map(children, child =>
_15
// this is the magic!
_15
React.cloneElement(child, { float: true })
_15
);
_15
return <StyledFloatingLabel>{childrenWithProps}</StyledFloatingLabel>;
_15
};
_15
export default FloatingLabel;
_15
_15
// styled-components wrapper to make sure it's positioned relative
_15
const StyledFloatingLabel = styled.div`
_15
position: relative;
_15
...rest of the styles
_15
`;

So basically, take each child (in our case, Input and Label) and clone them, then add on the float prop (an optional prop) and set it to true, which only gets added if Input and Label are rendered within the context of FloatingLabel.

Inside Input and Label, we can access the float prop and if it's true, set the float styles (aka shrink the label to top left on input focus, show the placeholder on input focus, etc).

To give you an idea of what that looks like, let's look at the Label component and how we could style that:


_22
const Label = props => (
_22
// Access the props with spread attributes
_22
// StyledLabel is a styled-components wrapper which then has
_22
// access to the props (in particular, float)
_22
<StyledLabel {...props}>{props.children}</StyledLabel>
_22
);
_22
export default Label;
_22
_22
const StyledLabel = styled.label`
_22
font-size: 1.2rem;
_22
padding: 0 1rem;
_22
transition: all 0.2s;
_22
color: #8e2de2;
_22
/* Here we can access the props, if float: true, then add some float-specific styles */
_22
${props =>
_22
props.float &&
_22
`
_22
position: absolute;
_22
top: 0;
_22
left: 0;
_22
`}
_22
`;

This saves us from having to pass the float prop explicitly to each child when called within FloatingLabel, and promotes reusability outside of that context, in case we need to render a label or input without floating, as you can see here:

Screenshot showcasing label and input reusability with non-floating labels

Conclusion

I think using React.cloneElement for context-dependent props is a pretty neat way to cut down on component clutter. Passing those implicitly is one less thing a user has to reason about when navigating your API.

I'm interested in hearing your thoughts about this method! Do you prefer to pass all props explicitly?