Skip to content

Instantly share code, notes, and snippets.

@kylpo

kylpo/blog.md Secret

Created June 21, 2017 23:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kylpo/4eee3b7745bd07f3c5c071c82c19f229 to your computer and use it in GitHub Desktop.
Save kylpo/4eee3b7745bd07f3c5c071c82c19f229 to your computer and use it in GitHub Desktop.

All about React's cloneElement()

What is it?

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

https://gist.github.com/0ffd4d146b182601e171b71bcdaf9943

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? Read more about Injector Components here.

How does it work?

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

https://gist.github.com/c596a4f713cd0171bc3d19f42e4f1ce2

https://gist.github.com/ee928f62877b478941f1cf6ac3309b05

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.

https://gist.github.com/3a5a720ecf6315c888a06973e3fd1666

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).

https://gist.github.com/4eb85cfa0ffbb0054199602d5ee070f7

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:

https://gist.github.com/8ed87c506a594eb9ea36c57de52fab42

https://gist.github.com/c3c313dd821de29ee3f28961c7a39fc8

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: https://gist.github.com/2fc6dfa919e9bfd5d4cb69ad73b52e3c

https://gist.github.com/3670801decdc918b07185f2089fee9c1

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?

https://gist.github.com/4ab5563a2a2e953baae0dce497489706

https://gist.github.com/29c11e8b46a7521f8cc44115efe38e70

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: https://gist.github.com/ab31af075ef1819d237db1ffb454ae1a

https://gist.github.com/d3c6710dc24f9b2d2f35b3f1c233d937

And now the result of the bad case, where Cloning_ returns a new object with a new object: React.cloneElement(child, {nestedNewObject: {}}) https://gist.github.com/7dc382185913b208a45e0b591e003b6e

See this github issue for more.

Nesting

Think about nested cloneElement()s: https://gist.github.com/4d8ec999e8885842cab1df531d31f472

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment