The question is quite old, but since you didn't seem to get a satisfying answer I'll give it a shot too.
As you have observed by yourself, changing
// Scenario A
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
to
// Scenario B
<div onClick={() => this.setState({})}>
<Child />
</div>
will in fact, end up with
Container render
Child render
in the console, every time you click.
Now, to quote you
As fas as I understand, if setState() is triggered, render function of
Container component is called and all child elements should be
re-rendered.
You seemed to be very close to understanding what is happening here.
So far, you are correct, since the Container
's render
is executed, so must the components returned from it call their own render
methods.
Now, as you also said, correctly,
<Child />
// is equal to
React.createElement(Child, {/*props*/}, /*children*/)
In essence, what you get from the above is just an object
describing what to show on the screen, a React Element.
The key here is to understand when the React.createElement(Child, {/*props*/}, /*children*/)
execution happened, in each of the scenarios above.
So let's see what is happening:
class App extends Component {
render() {
return (
<Container>
<Child/>
</Container>
)
}
}
class Container extends Component {
render() {
console.log('Container render');
return (
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
)
}
}
class Child extends Component {
render() {
console.log('Child render');
return <h1>Hi</h1>
}
}
You can rewrite the return value of App
like this:
<Container>
<Child/>
</Container>
// is equal to
React.createElement(
Container,
{},
React.createElement(
Child,
{},
{}
)
)
// which is equal to a React Element object, something like
{
type: Container,
props: {
children: {
type: Child, // |
props: {}, // +---> Take note of this object here
children: {} // |
}
}
}
And you can also rewrite the return value of Container
like this:
<div onClick={() => this.setState({})}>
{this.props.children}
</div>
// is equal to
React.createElement(
'div',
{onClick: () => this.setState({})},
this.props.children
)
// which is equal to React Element
{
type: 'div',
props: {
children: this.props.children
}
}
Now, this.props.children
is the same thing as the one included in the App
's returned React Element:
{
type: Child,
props: {},
children: {}
}
And to be exact, these two things are referentially the same, meaning it's the exact same thing in memory, in both cases.
Now, no matter how many times Container
get's re-rendered, since its children
are always referentially the same thing between renders (because that React Element was created in the App
level and it has no reason to change), they don't get re-rendered.
In short, React doesn't bother to render a React Element again if it is referentially (===
) equal to what it was in the previous render.
Now, if you were to change the Container
you would have:
<div onClick={() => this.setState({})}>
<Child />
</div>
// is equal to
React.createElement(
'div',
{onClick: () => this.setState({})},
React.createElement(
Child,
{},
{}
)
)
// which is equal to
{
type: 'div',
props: {
children: {
type: Child,
props: {},
children: {}
}
}
}
However in this case, if you were to re-render Container
, it will have to re-execute
React.createElement(
Child,
{},
{}
)
for every render. This will result in React Elements that are referentially different between renders, so React will actually re-render the Child
component as well, even though the end result will be the same.
Reference