Search code examples
javascriptreactjsfluxreactjs-flux

React event hierarchy issue


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?


Solution

  • 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.