Rodolphe
Rodolphe

Reputation: 1731

Component has updated but no visual change

I'm pretty new to React and I'm pulling my hair out over this one:

HTML

<div id="root"></div>

JS

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.state = { title: props.title };
  }

  render() {
    return ()<div>{this.state.title}</div>);
  }
}

class TestApp extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      children: [
        <Child title='a' />,
        <Child title='b' />
      ]};
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('[componentDidUpdate]');
    console.log('prevChildren: ' + prevState.children.map(c => c.props.title).join(', '));
    console.log('children: ' + this.state.children.map(c => c.props.title).join(', '));
  }

  handleOnClick = () => {
    console.log('[handleOnClick]');

    this.setState((prevState) => {
      return {
        children: [
          <Child title='c' />,
          <Child title='d' />
        ]
      };
    });
  };

  render() {
    console.log('[render]');

    return (
      <div>
        <div>TEST</div>
        {this.state.children}
        <button onClick={this.handleOnClick}>CHANGE</button>
      </div>
      )
  }
}

ReactDOM.render(<TestApp />, document.getElementById('root'));

CodePen: https://codepen.io/robloche/pen/xmGMBy

What's happening in the console when I click the button is:

[handleOnClick]
[render]
[componentDidUpdate]
prevChildren: a, b
children: c, d

which looks pretty good to me but somehow, a and b are still displayed instead of c and d... What did I miss?

Upvotes: 3

Views: 85

Answers (2)

Just code
Just code

Reputation: 13801

You should not use state in child component. you do not have any dependency of the child props, just use parent component's props and it should work fine.

Change:

return (<div>{this.state.title}</div>);

to

return (<div>{this.props.title}</div>);

The problem you are facing right now is that parent is changed to c,d and it has been passed as child too, but because react doesn't have any keys update or state updates it does not re-render the component. The best approach is to use the props passed from the parent and use them.

Demo

Upvotes: 3

rorschach
rorschach

Reputation: 2947

Since you have an array of Child elements then React can't distinguish when they've actually been updated/replaced with new ones.

Add a unique key to each Child and it will work, e.g.

<Child key='a' title='a'/>,
<Child key='b' title='b'/>,
etc.

NB! When dealing with arrays of Components then the key property is mandatory and while it will help in this case as well, your current approach is not very optimal.

Instead of creating whole new Components on a state change you should instead store only their values (title in this case) and then have the render() method deal with it.

The following is pseudo-pseudo code as I only added the pertinent parts to show how it would work.

constructor() {
  this.state = {
    children: ['a', 'b']
  }
}

onClick() {
  this.setState({
    children: ['c', 'd']
  });
}

render() {
  return (
    <>
      {this.state.children.map(title => <Child key={title} title={title}/>)}
    </>
  );
}

Upvotes: 3

Related Questions