Search code examples
reactjsreactjs-flux

Flux: Trigger parent methods from children in JSX


Suppose you have a Dropdown React component:

var Dropdown = React.createClass({
  render: function () {
    return (
      <div className="dropdown" />
    );
  },

  itemSelected: function (item) {
    this.setState({
      value: item.props.value
    });
  }
});

And a corresponding Option:

var Option = React.createClass({
  _onClick: function () {
    this.props.onSelect(this);
  },

  render: function () {
    return (
      <div className="item" onClick={this._onClick}>{this.props.value}</div>
    );
  }
});

Now imagine you use these components together like so:

var ColorForm = React.createClass({
  render: function () {
    return (
      <div>
        <label>Pick a color</label>
        <Dropdown>
          <Option onSelect={???.itemSelected} value="red" />
          <Option onSelect={???.itemSelected} value="blue" />
          <Option onSelect={???.itemSelected} value="green" />
        </Dropdown>
      </div>
    );
  }
});

Notice the onSelect={???.itemSelected} bit.

How do I pass the parent dropdown's itemSelected method to each child?

I know that if I create a custom dropdown class, it's easy, but this defeats the purpose of components:

var ColorDropdown = React.createClass({
  render: function () {
    return (
      <div className="dropdown">
        <Option onSelect={this.itemSelected} value="red" />
        <Option onSelect={this.itemSelected} value="blue" />
        <Option onSelect={this.itemSelected} value="green" />
      </div>
    );
  },

  itemSelected: function (item) {
    this.setState({
      value: item.props.value
    });
  }
});

Solution

  • You can add an onSelect attribute to child Option elements inside the Dropdown::render method using a combination of this.props.children, React.Children.map, and React.cloneWithProps:

    var React = require('react/addons');
    
    var Option = require('./Option');
    
    var Dropdown = React.createClass({
    
      itemSelected: function (item) {
        this.setState({
          value: item.value
        });
      },
    
      render: function () {
        var self = this;
        var children = React.Children.map(this.props.children, function (child) {
          if (child.type !== Option.type) {
            return child;
          } else {
            return Reat.cloneWithProps(child, {
              onSelect: function () {
                self.itemSelected(this);
              }
            });
          }
        });
    
        return (
          <div className="dropdown">
            {children}
          </div>
        );
      }
    });
    

    So now you don't need to set onSelect for each Item child:

    var ColorForm = React.createClass({
      render: function () {
        return (
          <div>
            <label>Pick a color</label>
            <Dropdown>
              <Option value="red" />
              <Option value="blue" />
              <Option value="green" />
            </Dropdown>
          </div>
        );
      }
    });
    

    See the similar code for radio buttons: http://jaketrent.com/post/send-props-to-children-react/