setState not triggering a re-render when data has been modified

14,253

Solution 1

I think the problem could be the your are only using getInitialState for the Row. You are setting state with the passed in props. If the children get new props, getInitialState won't get called again. I always refer back to https://facebook.github.io/react/docs/component-specs.html to see all the lifecyle events. Try using componentWillReceiveProps(object nextProps) to set the state again. If you don't actually need state in the child, remove the use of state and just use props and it probably will work.

Solution 2

If you're children are using State and state is not updating, what about props? does that render just fine? If the parent changes the state of the children and the children don't reflect that change those child components most likely need a unique key assigned to them which tells React that these items need to be updated when the state is updated on the parent. I ran into a similar issue where props inside the child updated to reflect the parent but the state of the child would not update. Once I added keys, the solution was resolved. For further reading on this check out this blog that initially hinted the problem to me.

http://blog.arkency.com/2014/10/react-dot-js-and-dynamic-children-why-the-keys-are-important/

There is also the official documentation from React that explains this situation.

http://facebook.github.io/react/docs/multiple-components.html#dynamic-children

Share:
14,253
Joel
Author by

Joel

Updated on July 08, 2022

Comments

  • Joel
    Joel almost 2 years

    Just started working with React, and it's been going relatively smooth up until this point.

    I have a list, that contains X pre-defined items, which means that the list is always rendered with a given amount of rows.

    This data is collected from a REST API, prior to the list rendering. The data contains variables relating to the list, as well as an array that contains each item within the list.

    I chose the easy route, so I populate every component with a single JSON object named 'data', that contains everything necessary.

    Rendering the list looks something like this:

    <MyList data={data} />
    

    Then, in the getInitialState of MyList:

    dataInt: this.props.data.dataInt || 0,
    dataStr: this.props.data.dataStr || '',
    rows: this.props.data.rows || []
    

    So I store my array of items (JSON) in the initial state of the list (parent), and when I choose to render the list, I first create all components (children) in an array, like this:

    var toRender = [];
    for(i = 0; i < this.state.rows.length; i++) {
        toRender.push(<ItemRow data={this.state.rows[i]} />);
    }
    

    and then I render like this:

    return (
        <div className="item-container">
            <table className="item-table">
                {toRender}
            </table>
        </div>
    );
    

    The render-function of MyItem look something like this:

    return (
        <tr>
            <td>{this.state.dataFromItem}</td>
        </tr>
    );
    

    Now, let's say I want to modify the child data from within the parent, with some new data I just got from the API. Same structure, just the value of one field/column has changed:

    i = indexOfItemToBeUpdated;
    var newRows = this.state.rows;
    newRows[i] = data; // New JSON object with same keys, different values.
    this.setState({ rows: newRows }); // Doesn't trigger re-render
    this.forceUpdate(); // Doesn't trigger re-render
    

    What am I doing wrong?

    At first I thought it was because I was wrapping the render function of MyItem in a , but since it renders perfectly fine on the initial render, I will assume that is an acceptable wrapper.

    After some testing, it seems that the parent view is re-rendered, but not the children (which are based on the data that is updated within the parent).

    JSfiddle: https://jsfiddle.net/zfenub6f/1/