1

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?

4
  • are you sure in the SearchItem the promise then gets executed (and calls props.onStatusUpdate) ? Commented Jun 12, 2018 at 14:58
  • yep, that all works to plan. Just if I put a breakpoint in the child component constructor during the refresh / render method, it never gets hit Commented Jun 12, 2018 at 15:08
  • 1
    constructor of the child will get called only once. Just put a breakpoint on render method of child. Commented Jun 12, 2018 at 15:11
  • ok! that explains something! Commented Jun 12, 2018 at 15:24

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.