Reputation: 5751
I have a React component that contains an array of child components. The parent retrieves data from a service and stores it in state. It passes an item from the data array to each child component via props.
The child component includes functionality that updates a value in its data item. When it does this, it fires an event, passing the updated item back to the parent. The parent creates a new state array, including the updated item.
Simplified code below.
This all works fine, and the update array is processed in the parent's render method. However, the child components are never re-rendered, so the updated property remains at its previous value.
How can I get the relevant child component to display the updated status?
class SearchView extends Component {
pageSize = 20;
constructor(props) {
super(props);
this.state = {
searchTerm: this.props.searchTerm,
results: []
};
}
getResults = (page) => {
const from = (page - 1) * this.pageSize;
searchActions.termSearch(this.state.searchTerm, from, this.pageSize).then(response => {
const results = response.SearchResultViews;
this.setState({
results: results
});
});
}
componentDidMount() {
this.getResults(1);
}
refresh(result){
const results = this.state.results.map(r => {
return (r.Id === result.Id) ? result : r;
});
this.setState({
results: results
});
}
render() {
let items = [];
if (this.state.results.length > 0) {
items = this.state.results.map((result, i) => {
return <SearchItem key={i} result={result} onStatusUpdate={(r) => this.refresh(r)}></SearchItem>;
});
}
return (
<div className="r-search-result">
<Row className='clearfix scroller'>
<div className='r-container-row results'>
{ items }
</div>
</Row>
</div>
);
}
}
class SearchItem extends Component {
constructor(props) {
super(props);
}
updateStatus(newValue) {
resourceActions.updateStatus(newValue);
//Bubble an event to the Parent to refresh the result and view
if (props.onStatusUpdate) {
searchActions.get(props.result.Id).then((result) => {
props.onStatusUpdate(result);
});
}
}
render() {
return (
<a href={this.props.result.link}>
<span className="column icon-column">{this.props.result.imageUrl}</span>
<span className="column title-column">{this.props.result.titleLink}</span>
<span className="column status-column">{this.props.result.status}</span>
<span className="column button-column"><button onClick={() => this.UpdateStatus(5)}></button></span>
</a>
);
}
}
Edit
In my actual (non-simplified) app, the child component transforms the props it has been passed in the ComponentDidMount()
method, and it sets values in state; the render method binds the markup against state, not props. After putting a breakpoint in the child's Render()
method as suggested by @Vishal in the comments, I can see that the updated data is received by the child, but since the state hasn't been updated, the component doesn't display the updated data.
The question then is, how best to update the component state without causing an infinite render loop?
Upvotes: 1
Views: 2447
Reputation: 5751
In the end, I solved the problem by transforming the properties into state for the child component's in componentWillUpdate()
, as well as the componentDidMount()
method. As illustrated in the code below:
componentDidMount() {
if (this.props.result) {
this.prepareRender();
}
}
componentWillUpdate(nextProps, nextState) {
if (nextProps.result.status!== this.props.result.status) {
this.prepareRender();
}
}
prepareRender() {
//simplified
this.setState({
imageUrl: this.props.result.imageUrl,
titleLink: this.props.result.titleLink,
status: this.props.result.status
});
}
render() {
return (
<a href={this.props.result.link}>
<span className="column icon-column">{this.state.imageUrl}</span>
<span className="column title-column">{this.state.titleLink}</span>
<span className="column status-column">{this.state.status}</span>
<span className="column button-column"><button onClick={() => this.UpdateStatus(5)}></button></span>
</a>
);
}
UPDATE
In React 16.3 the componentWillUpdate()
method is deprecated. This solution should use the new getDerivedStateFromProps()
lifecycle method, as explained here: https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html.
Upvotes: 1