Reputation: 1731
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
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.
Upvotes: 3
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