Aessandro
Aessandro

Reputation: 5761

React js attach click event on every single span element

I have the following react code at: http://codepen.io/AlexanderWeb00/pen/ZOWyNr

I am trying to attach for every span a click event that allows to console.log the correspondent number that you have clicked on:

var one = [],
    two = [];

var SingleButton = React.createClass({
  getInitialState: function() {
    return {
      val1: one,
      val2: two,
      action: ''
    }  
  },
  handleClick: function() {
     var val1 = this.state.val1,
         val2 = this.state.val2;

    if(!$('.action').length){
      val1.push(this.props.numbers);
      console.log(val1[0].join(" "));
    }
  },
  render: function() {
    var numbers = [];
    var classes = 's-btn large-4 columns';
    var ff =this.handleClick;
    this.props.numbers.forEach(function(el){
      numbers.push(<span onClick={ff} className={classes}>{el}</span>);
    });
    return (
      <div className="row">
        {numbers}
      </div>
    );
  }
});

what I am getting instead is 123456789 as opposed to 5 if I only click on number 5.

Upvotes: 2

Views: 2157

Answers (1)

omerts
omerts

Reputation: 8838

As stated in the comment, you are pushing the whole numbers arrays into val1, therefore it will always display the whole list. To solve it we need to pinpoint the clicked span, get its number, and add it to the list.

The first method would be using jQuery, and the eventArg that the onClick sends. Once the span is clicked our handleClick is called, with the click event data as a parameter. We will extract the clicked span from the event arg, ie eventArg.target, and get its text using jQuery (ie $(eventArg.target).text()).

To specifically solve the issue you could:

var one = [],
    two = [];

var SingleButton = React.createClass({
  getInitialState: function() {
    return {
      val1: one,
      val2: two,
      action: ''
    }  
  },
  handleClick: function(e) {
     var val1 = this.state.val1,
         val2 = this.state.val2;

    if(!$('.action').length){
      val1.push($(e.target).text());
      console.log(val1.join(" "));
    }
  },
  render: function() {
    var numbers = [];
    var classes = 's-btn large-4 columns';
    var ff =this.handleClick;
    this.props.numbers.forEach(function(el){
      numbers.push(<span onClick={ff} className={classes}>{el}</span>);
    });
    return (
      <div className="row">
        {numbers}
      </div>
    );
  }
});

https://jsfiddle.net/omerts/zm0bzmwp/

Another option is to wrap the span's number in a colsure. In other words, create an anonymous function, which will have the number as part of its scope object. Since closures are out of the scope of this question, for further reading: Closures

var SingleButton = React.createClass({
  getInitialState: function() {
    return {
      val1: [],
      val2: [],
      action: ''
    }  
  },
  handleClick: function(number) {
    if(!$('.action').length){
      const updatedList = this.state.val1.slice(); // clone the array, and add the new number
      updatedList.push(number);
      console.log(updatedList.join(" "));
      this.setState({val1: updatedList});      
    }
  },
  render: function() {
    var numbers = [];
    var classes = 's-btn large-4 columns';

    this.props.numbers.forEach(function(el){    
      numbers.push(<span onClick={() => {this.handleClick(el)}} className={classes}>{el}</span>);
    }, this);

    return (
      <div className="row">
        {numbers}
      </div>
    );
  }
});

https://jsfiddle.net/omerts/590my903/

Second option is better, since you should not be mutating state, unless you are using this.setState. If you don't really need it as part of your state, just make val1, and val2 data memebers of your SingleButton.

Upvotes: 2

Related Questions