7

What is the best way to handle state changes in a deep node which also need to be handled by a parent node. Here is my situation:

<Table>
  <Row prop={user1}>
    <Column prop={user1_col1} />
    <Column prop={user1_col2} />
  </Row>
  <Row prop={user2}>
    <Column prop={user2_col1} />
    <Column prop={user2_col2} />
  </Row>
  <TableFooter>
    <FooterColumn prop={sum1} />
    <FooterColumn prop={sum2} />
  </TableFooter>
</Table>

Whenever someone is changing anything in the column property I only have to maintain the state of this value within that Column component. However, I now would like a sum of these values in the FooterColumn component. What is the best way to achieve this?

If I'm going to pass up the state change I must keep states in multiple places and then pass it down, this is a lot of tedious work. Is it best to use EventEmitters or am I missing something?

user517153
  • 205
  • 2
  • 8

1 Answers1

5

So, all you need is to keep track of the state in the parent component, and share the state update function with the children:

var Parent = React.createClass({
  getInitialState: function() {
    return {
      users: [
        {name: 'Matt', values: [1, 2]},
        {name: 'user517153', values: [4, 5]}
      ]
    };
  },
  updateValue: function(rowId, colId, newValue) {
    var newUsersState = this.state;
    newUsersState.users[rowId].values[colId] = newValue;
    this.setState({users: newUsersState});
  },
  render: function() {
    var rows = this.state.users.map(function(user, r) {
      var cols = user.values.map(function(value, c) {
        return (
          <Column key={c} prop={value} rowId={r} colId={c} onChange={this.updateValue}/>
        );
      });

      return (
        <Row key={r} prop={user}>
          {cols}
        </Row>
      );
    });

    // Yes, it could be more efficient if you did it all in one map/forEach - doing this in a second one for clarity
    var footerCols = this.state.users.map(function(user) {
      var sum = 0;
      user.values.forEach(function(value) { sum+= value; });
      return (
        <FooterColumn prop={sum} />
      );
    });

    return (
      <Table>
        {rows}
        <TableFooter>
          {footerCols}
        </TableFooter>
      </Table>
    );
  }
});

In your Column class, you simply need something along the lines of:

var Column = React.createClass({
  onChange: function(event) {
    var props = this.props;

    var newValue = event.target.value; // Get the new value somehow - this is just an example
    props.onChange(props.rowId, props.coldId, newValue);
  },
  render: function() {
    var props = this.props;

    return (
      <td onChange={this.onChnage}>{props.prop}</td>
    );
  }
});

Hope that makes sense.

Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128
  • I second this. You should strive to keep all the application logic in the topmost component, and have the nested components just render their properties and fire events when something happens within them, so that the parent can respond to the changes with a callback. – Gian Marco Toso Dec 23 '15 at 08:23
  • Thanks! But the thing is, I also have a Row component in between, this means passing it down 2 times. If I should create another subcomponent, it will be 3 times, these seems really tedious. Is this the point where you should already use something like Flux? – user517153 Dec 23 '15 at 09:42
  • So, first off, `Row` in the case as used above is not actually a parent of `Column`, and so `Row` has no need to know anything about the props of `Column`. In React, a Component is the parent of another component if that component renders it's child directly. So, you could have a component called `DivMaker` with 100 nested divs, but the parent for all those divs would be `DivMaker`, regardless of their nested depth. – Matthew Herbst Dec 23 '15 at 16:39
  • The second question you've got is more interesting. Yes, eventually it becomes hard to manage. Flux is a good option (I prefer Reflux). Also look at React's [`context`](https://facebook.github.io/react/docs/context.html) feature which is designed specifically to mitigate having to deal with passing props through large component trees. – Matthew Herbst Dec 23 '15 at 16:39