With the goal to better understand react reconciliation I created this example
// Just a simple timer component basically which shows each second passed
class Stateful extends React.Component{
constructor(props){
super(props)
this.state={timer:0}
}
componentDidMount(){
let that = this;
setInterval(function(){
that.setState(function(prevState){return {timer: prevState.timer+1}})
}, 1000);
}
render(){
return <p>{this.state.timer}</p>
}
}
// Just a demo class for understanding reconciliation
class Demo extends React.Component {
constructor(props){
super(props)
this.state={}
}
componentDidMount(){
let that = this;
setTimeout(function(){
that.setState({showWarning: true})
}, 3000);
}
render() {
if (this.state.showWarning) {
return (
<div>
<Stateful /> // I was hoping this would create a new instance of Stateful Component after 3 seconds
</div>
);
}
return (
<div>
<Stateful />
</div>
);
}
}
ReactDOM.render(
<Demo />,
document.getElementById('container')
);
You can see after three seconds showWarningis set to true. So I was believing that after three seconds I would get a new instance of <Stateful> component (because it lives in a different div than the one rendered already) - hence I would see the output of Statefulcomponent starting from 0 again, but the timer just continued to increase on the screen.... So the output basically is:
0...1...2...3...4....(and so on each second).
Whereas I expected it to show 0...1..2.. and on third second do a restart basically and start 0...1...2...3...etc.
What did I miss from reconciliation docs that led me believe in this? (I have a feeling the react docs on this misses to highlight this, or it might be I missed something?)
From React's point of view,
<div>
<Stateful />
</div>
on the lines inside the if condition have the same type and inner structure as these elements:
<div>
<Stateful />
</div>
outside of it.
They鈥檙e both elements, both have type of 'div', and both contain a single child with type of Stateful (which is also the same type both times).
React just compares their types and structure鈥攊t doesn't care if elements are defined on the same lines or not.
@gaearon hm Yeah and in both cases here it seems Stateful has same key.
I think I was somehow confused by this example: https://github.com/facebook/react/issues/11540, but that is a different situation that I look at it now, right? In this latter example react will key all 4 buttons (regardless of the fact whether all 4 of the buttons are being rendered or not) using keys for example "1","2","3","4", am I right? and that is why it will be able to differentiate between them if value of state.testchanges?
react will key all 4 buttons (regardless of the fact whether all 4 of the buttons are being rendered or not) using keys for example "1","2","3","4", am I right
Yes, this sounds about right.
React just compares their types and structure鈥攊t doesn't care if elements are defined on the same lines or not.
@gaearon
1) dan but isn't react using information from lines in this example https://github.com/facebook/react/issues/11540 when keying all 4 buttons using keys "1","2","3","4"? (even though only 2 of them can be visible at a time).
2) Why isn't it doing same in this case? and say assigning key "1" to first occurence of Statefulin code, and key "2" to second occurence of Statefulin code?
it seems I am missing something
@gaearon AAh I think the reason in first case above it keys them using 1,2,3,4 is because they are all inside same return clause. Whereas in the second case they are in two different return clauses. am I right?
Yes, this sounds right. I think you'll find it easier to understand if you compile JSX in Babel REPL:
https://babeljs.io/repl/#?babili=false&browsers=&build=&builtIns=false&code_lz=Q&debug=false&circleciRepo=&evaluate=false&lineWrap=false&presets=react&prettier=false&targets=&version=6.26.0
This will show you how JSX is just createElement calls. In your example with 1, 2, 3, and 4, they occupy the same "spots" in one structure (that otherwise has holes when they're missing).
@gaearon I discovered another interesting thing.
Did you know below:
render() {
return (<div>
{this.state.test==1 ? <Stateful/> : <Stateful/>}
</div>);
}
if value of testchanges, it still uses _same instance_ of <Stateful/> component? So it didn't key <Stateful/> components like it keyed buttons in this case: https://github.com/facebook/react/issues/11540, with different keys for each button.
i.e. but if I do
render() {
return (<div>
{this.state.test==1 ? <Stateful/> : ""}
{this.state.test!=1 ? <Stateful/> : ""}
</div>);
}
it renders _different instances_ of <Stateful/> if testchanges. Why this difference as compared to first example above (with single ? operator) ?
using the babel transpiler site dan pointed out above:
render() {
return (<div>
{this.state.test==1 ? <Stateful/> : <Stateful/>}
</div>);
}
transpiles to
render() {
return React.createElement(
'div',
null,
this.state.test == 1 ? React.createElement(Stateful, null) : React.createElement(Stateful, null)
);
}
whereas
render() {
return (<div>
{this.state.test==1 ? <Stateful/> : ""}
{this.state.test!=1 ? <Stateful/> : ""}
</div>);
}
transpiles to
render() {
return React.createElement(
"div",
null,
this.state.test == 1 ? React.createElement(Stateful, null) : "",
this.state.test != 1 ? React.createElement(Stateful, null) : ""
);
}
to React, this is a different structure. diffing it takes the element in position 2 from a Stateful to "" and the element in position 3 from "" to a Stateful.
Consider every argument after the second one to createElement a "hole". Every hole gets an
implicit index.
The same hole gets filled with <Stateful /> in both cases:
render() {
return React.createElement(
'div',
null,
// one "hole"
this.state.test == 1 ? React.createElement(Stateful, null) : React.createElement(Stateful, null)
);
}
Two different holes get filled either with <Stateful /> or with null:
render() {
return React.createElement(
"div",
null,
// first "hole"
this.state.test == 1 ? React.createElement(Stateful, null) : "",
// second "hole"
this.state.test != 1 ? React.createElement(Stateful, null) : ""
);
}
@gaearon I see now. though having this stuff explained in docs would not have hurt though. I understand some of this stuff goes beyond the basics, but knowing them is needed anyway IMHO.
You can always send doc PRs :-) https://github.com/reactjs/reactjs.org
But again, the balance is very hard. We have to cater to the broadest audience possible. From my experience of replying in issues for a few years, 95% of people aren't ready to learn about these details when they're just learning React. The 5% like you could indeed benefit from a better explanation.
If you have an idea of how to concisely express these ideas, send a PR to the "Reconciliation" page. It's already marked as "advanced" so I think it's fine to add more details there.
@gaearon ok, I understand.