Shail Patel
Shail Patel

Reputation: 1814

Pass Parent Prop to Children reactjs

I have a Legend, which contains multiple Legend.Items children. I'm having a problem where currently onClick it is possible to deselect all of the Legend Items which has consequences that I'd like to avoid. Is it possible to set some sort of onClick handler in the Legend component that can have some state clicked and check whether there are n - 1 legend items "selected/faded", n being the total number of legend items? I looked at the JSX Spread Attributes, but because I'm using {this.props.children}, I'm not sure how to use them or if they would work in this context.

I also took a look at this blogpost (http://jaketrent.com/post/send-props-to-children-react/), but it looked a bit hacky to me and I thought there might be a more conventional way. I'm new to ReactJS so if I need to provide more context, let me know!

MY CODE:

LEGEND.JSX

var React = require('react');
var cn = require('classnames');

// Have legend hold state about how many clicked
var Legend = React.createClass({
  getInitialState: function () {
    return { clicked: 0 }
  },

  render: function () {
    console.log(this.props.children);

    return (
      <ul className="legend inline-list">
        {this.props.children}
      </ul>
    );
  },
});

Legend.Item = React.createClass({
  getInitialState: function () {
    return { hover: false, clicked: false };
  },

  handleMouseOver: function () {
    this.setState({ hover: true });
    this.props.mouseOver(this.props.name);
  },

  handleMouseOut: function () {
    this.setState({ hover: false });
    this.props.mouseOut(this.props.name);
  },

  handleClick: function() {
    if (this.state.clicked) {
      this.setState({ clicked: false });
      this.props.click(this.props.name);
    } else {
      this.setState({ clicked: true });
      this.props.click(this.props.name);
    };
  },

  render: function () {
    var swatchClasses = cn({ 'swatch': true, 'legend-item-fade': this.state.hover, 'c3-legend-item-hidden': this.state.clicked })
    var spanClasses = cn({ 'legend-item-fade': this.state.hover, 'c3-legend-item-hidden': this.state.clicked })

    return (
      <li className="legend-item">
        <i className={swatchClasses}
          onClick={this.handleClick}
          onMouseEnter={this.handleMouseOver}
          onMouseLeave={this.handleMouseOut}
          style={{ "backgroundColor": this.props.color }}></i>
        <span className={spanClasses}
          onClick={this.handleClick}
          onMouseEnter={this.handleMouseOver}
          onMouseLeave={this.handleMouseOut}>
          {this.props.name}
        </span>
      </li>
    );
  },
});

module.exports = {
  Legend: Legend,
};

RESPONSE.JSX RENDER FUNCTION

<Legend>
  {newColumns.map(function (column) {
    return (
      <Legend.Item name={column.name}
        color={column.color}
        click={this.onLegendClick}
        mouseOut={this.onLegendMouseOut}
        mouseOver={this.onLegendMouseOver}/>
    );
  }.bind(this))}
</Legend>

Upvotes: 1

Views: 869

Answers (1)

gcedo
gcedo

Reputation: 4931

I think the best and simplest way is to use callbacks.

In Legend recreate the components from the children, augmenting their props with a callback to Legend:

let legendItems = React.Children.map(this.props.children, child => 
    React.cloneElement(child, { updateLegendCounter: this.updateLegend})
);

The callback in Legend is something like this:

updateLegend() {
    this.setState({clicked: clicked + 1})
}

And finally, in your render method, you discriminate when

if (this.state.clicked === children.length-1)

Also, I would pass the initial state of clicked as a prop to the Item element. In this way it becomes really easy to select/deselect all.

Upvotes: 1

Related Questions