1

I'm still learning ReactJS. I'm challenging myself to write a very basic todo app (as one does) and I'm having an issue calling an onClick function.

var List = React.createClass({

  handleClick: function () {
    alert("Clicked!");
  },

  render: function () {

    var list = this.props.items;
    var items = list.map(function(item){
      return (
        <li style={{borderBottom:'1px solid red'}}>
          <label onClick={this.handleClick}>
            <input type="checkbox" />
            {item}
          </label>
        </li>
      );
    });

    return (
      <ul>{items}</ul>
    )
  }
});

The issue here is that onClick={this.handleClick} cannot be called because it is not inside the return call in the render function.

What do I need to do to access handleClick from inside the map function?

Pineda
  • 7,435
  • 3
  • 30
  • 45
Matt Saunders
  • 4,073
  • 7
  • 33
  • 47
  • Possible duplicate of [OnClick Event binding in React.js](http://stackoverflow.com/questions/27397266/onclick-event-binding-in-react-js) – ZenMaster Nov 16 '16 at 18:45

3 Answers3

2

The second argument for the map function is a value to define the scope of this when executing the callback.:

.map( callback( currentValue, index, array), value_for_this/scope_to_run_in )

So you can modify your map function as follows:

var items = list.map(function(item){
  return (
    <li style={{borderBottom:'1px solid red'}}>
      <label onClick={this.handleClick}>
        <input type="checkbox" />
        {item}
      </label>
    </li>
  );
}, this);

You could also use an arrow function which where this is implicitly bound:

var items = list.map((item) => {
  return (
    <li style={{borderBottom:'1px solid red'}}>
      <label onClick={this.handleClick}>
        <input type="checkbox" />
        {item}
      </label>
    </li>
  );
});
Pineda
  • 7,435
  • 3
  • 30
  • 45
  • That resolves the issue, but now the click is firing on page load as well (so getting 3 alerts in a row, one per list item, on page load) – Matt Saunders Nov 16 '16 at 21:23
  • That _is_ strange. You're not invoking the handleClick when setting it on the onClick by defining it with parenthesis ( onClick = {this.handleclick()} ) , are you? – Pineda Nov 16 '16 at 21:29
1

The problem you're running into is that your call to list.map will invoke the passed function with a different this than you have in your render method.

An easy fix is to grab this in the outer scope and stash it in a variable, then use that variable in your inline function.

render: function () {
    var self = this;
 // ^^^^^^^^^^^^^^^^

    var list = this.props.items;
    var items = list.map(function(item){
      return (
        <li style={{borderBottom:'1px solid red'}}>
          <label onClick={self.handleClick}>
                       // ^^^^
            <input type="checkbox" />
            {item}
          </label>
        </li>
      );
    });

    return (
      <ul>{items}</ul>
    )
}
Adam Maras
  • 26,269
  • 6
  • 65
  • 91
  • that's a nice trick. it feels a little too easy - are there any downsides to this? – Matt Saunders Nov 17 '16 at 19:04
  • @MattSaunders this is a pretty common pattern in pre-ES2015 code. It essentially does the same thing as what an ES2015 arrow function would do, you're just doing it by hand. There are other options (rebinding, passing `this` to `map`, etc) but this way is similarly performant and at least as readable. – Adam Maras Nov 17 '16 at 19:57
0

You should bind this explicitly to the handleClick function to be referring to the React component not the map function, so you can refactor your code as follow:

var items = list.map(renderListItem.bind(this));

And add renderListItem method in your React class as follow:

renderListItem(item) {
  return (
    <li style={{borderBottom:'1px solid red'}}>
      <label onClick={this.handleClick}>
        <input type="checkbox" />
        {item}
      </label>
    </li>
  );
}
Basim Hennawi
  • 2,651
  • 3
  • 19
  • 31