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.
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
.
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
The offical docs have a template of how updates happen. Let's modify it with our use case:
ReactDOM.render()
is called with with the<App />
element- Using the
App
element, React creates and renders theApp
component - Our
App
component createsDiv
, thenCloning_
elements and returns the tree (<Cloning_><Div hi='hi' /></Cloning_>
) as the result. - Using the
Cloning_
element, React creates and renders theCloning_
component with{hi: 'hi'}
as the props. - Our
Cloning_
component returns a cloned<Div />
element, with{hi: 'hi'}
passed as props, as the result. - Using the
Div
element, React creates and renders theDiv
component - Our
Div
component returns a<div />
element as the result. - React DOM efficiently updates the DOM to match the tree of elements.
Basically just a React.createElement with a single for
loop to copy over props passed. See its source, and this tweet thread.
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.)
When cloning a
PureComponent
, the 2nd argument of thecloneElement()
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.
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.
Props to the current official docs, which explain all of this nicely. I just needed more examples to solidify everything.