React event hierarchy issue
Asked Answered
A

1

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?

Aquatic answered 23/12, 2015 at 7:45 Comment(0)
M
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.

Maupassant answered 23/12, 2015 at 8:20 Comment(4)
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.Alanalana
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?Aquatic
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.Maupassant
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 feature which is designed specifically to mitigate having to deal with passing props through large component trees.Maupassant

© 2022 - 2024 — McMap. All rights reserved.