All about React’s cloneElement()

React.cloneElement() allows us to clone a runtime element (not the class), and apply an enhancement to it.

The code above renders a div with the onPress wired up. It is a contrived example, as a we can just pass in an onPress directly to the div, but what if <Press_ /> computed something complicated, then translated the result to something div could use? That is quite a powerful abstraction!

Also, this <Press_ /> component allows us to clearly separate concerns and reuse them on other elements. We might reuse our Press_ on an image or span, for example. We might have other cloneElement() components for concerns like <Style_ />, <Animate_ />, <Touch_ />, etc.

Wondering about this Name_ convention? Check out Injector Components.

How does it work?

Given this page, what do we expect to see in console?

Hmmm, how can Cloning_ have the child Div without Div being rendered first? Ahhhh, it must create an instance of it, which Cloning_ can use. So, lets console.log() in each of their constructor()s.

Nope, that wasn’t it.

And why can we see its props, especially the defaultProps!? What about its state? If we gave it a state of state = {why: 'why'}, could Cloning_ see it?

First of all, no. Cloning_ can not see it's child's state. Why? Well, because it doesn't exist yet. Keep in mind that JSX maps to React.createElement(), which returns an element (a simple object description React uses to apply to DOM).

When App is rendered (from ReactDOM.render()), in creating a Cloning_ element, it must first create the Div element. It is normal javascript execution order, but still nice to see:

Back on topic: Cloning_ is acting on the element of Div, and returning a new element. The returned element is a clone, with potentially modified passed in props.

Oh, is it like Object.assign() then? Almost like Object.assign({}, divElement, {newProp: 'newProp'})?

Yes, actually. The clone’s props will even override the child props. Seen here:

Notice the change in the value of hi.

OK, one last thing. See that bye: "bye" part? That crept in because Div has static defaultProps = {bye: 'bye'}. Thanks to it being a static, React.CreateElement() is able to use it for the returned element.

Order of operations in rendering elements, components, parents, and children?

Allllright, lets finish this exploration. What do you expect to be logged in this example?

Putting it all together

The offical docs have a template of how updates happen. Let’s modify it with our use case:

  1. ReactDOM.render() is called with with the <App /> element
  2. Using the App element, React creates and renders the App component
  3. Our App component creates Div, then Cloning_ elements and returns the tree (<Cloning_><Div hi='hi' /></Cloning_>) as the result.
  4. Using the Cloning_ element, React creates and renders the Cloning_ component with {hi: 'hi'} as the props.
  5. Our Cloning_ component returns a cloned <Div /> element, with {hi: 'hi'} passed as props, as the result.
  6. Using the Div element, React creates and renders the Div component
  7. Our Div component returns a <div /> element as the result.
  8. React DOM efficiently updates the DOM to match the tree of elements.

Performance Considerations

How does cloneElement() compare to createElement()?

Basically just a React.createElement with a single for loop to copy over props passed. See its source, and this tweet thread.

Re-renders

Based on what we learned in TODO scu doc, these cloning wrappers will be re-rendering often. Consider caching the computation outside of render so its render can do as little work as possible. (Note: I have not tried this yet, but plan to.)

cloneElement() of a PureComponent child

When cloning a PureComponent, the 2nd argument of the cloneElement() can be a new object, but no value of that object should be a new object, array, or function.

Below is an example of safe usage:

And now the result of the bad case, where Cloning_ returns a new object with a new object: React.cloneElement(child, {nestedNewObject: {}})

See this github issue for more.

Nesting

Think about nested cloneElement()s:

To be a good cloning citizen, be sure to pass props through that are not related to yours! In the above example, Cloning2_ should pass Cloning_'s props to Div, as well as its own.

References

Props to the current official docs, which explain all of this nicely. I just needed more examples to solidify everything.


Originally published in the react-playbook.