this.props.children not re-rendered on parent state change

The <Child /> component is not re-rendered because the props have not changed. React uses the concept of Virtual DOM which is a representation of your components and their data.

If the props of a component do not change the component is not re-rendered. This is what keeps React fast.

In you example there are no props sent down to Child, so it will never be re-rendered. If you want it to re-render each time (why would you ?), you can for example use a pure function

const Child = () => <h1>Hi</h1>;

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

Tags:

Reactjs