5

I used this article as an example (React way), but it is not working for me. Please point me to my mistake, as I can't understand what's wrong.

This is the error I see:

Uncaught TypeError: this.props.onClick is not a function

Here is my code:

// PARENT
var SendDocModal = React.createClass({
  getInitialState: function() {
    return {tagList: []};
  },
  render: function() {
    return (
      <div>
        {
          this.state.tagList.map(function(item) {
            return (
              <TagItem nameProp={item.Name} idProp={item.Id} onClick={this.HandleRemove}/>
            )
          })
        }
      </div>
    )
  },
  HandleRemove: function(c) {
    console.log('On REMOVE = ', c);
  }
});

// CHILD
var TagItem = React.createClass({
  render: function() {
    return (
      <span className="react-tagsinput-tag">
        <span>{this.props.nameProp}</span>
        <a className='react-tagsinput-remove' onClick={this.HandleRemove}></a>
      </span>
    )
  },
  HandleRemove: function() {
    this.props.onClick(this);
  }
});

Thanks in advance!

Soviut
  • 88,194
  • 49
  • 192
  • 260
user2787338
  • 53
  • 1
  • 1
  • 5

2 Answers2

11

The issue is that this inside the map callback does not refer to the React component, hence this.HandleRemove is undefined.

You can set the this value explicitly by passing a second argument to map:

this.state.tagList.map(function() {...}, this);

Now this inside the callback refers to the same value as this outside the callback, namely the SendDocModal instance.

This has nothing to do with React, it's just how JavaScript works. See How to access the correct `this` context inside a callback? for more info and other solutions.

Community
  • 1
  • 1
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Hi, Felix. Thanks for your reply. Tried without for loop(just a component with hardcoded props) and it worked great. But I don't understand how to pass the context in my case. Could you please describe this in more detail? – user2787338 Jul 22 '15 at 14:31
  • Sorry, simply confused forEach and map. It's the same solution. Have a look at the link in my answer to learn more about `this`. – Felix Kling Jul 22 '15 at 14:34
  • If I understood right I should do something like this: `code {this.state.tagList.map(function(item){ return( ) }, this)}, ` but in this case I get the same : Uncaught TypeError: this.props.contextProp.props.onClick is not a function – user2787338 Jul 22 '15 at 14:43
  • No, just implement the solution in my answer. The body of the callback stays the same. – Felix Kling Jul 22 '15 at 14:49
  • aha, now I got it. the correct code will be: `code {this.state.tagList.map(function(item){ return( ) }, this)} ` – user2787338 Jul 22 '15 at 14:50
  • But actually I didn't got it. if this stands now for parent component, how I'm passing child's "this" to parent, if now "this" stands for the reference to parent -) I'm totally confused. – user2787338 Jul 22 '15 at 14:51
  • Every function has its own `this`. Really the only issue was that you were *not* passing a function to `TagItem` because `this` inside the `map` callback did not refer to the `SendDocModal` component. By setting `this` of the callback correctly, `this.HandleRemove` inside the `map` callback now correctly references the `HandleRemove` method of `SendDocModal`. That's all to it. – Felix Kling Jul 22 '15 at 14:59
  • This just saved my life +1 dude – Alain Goldman Feb 15 '16 at 06:27
1

Try the following:

    var SendDocModal = React.createClass({
        getInitialState: function() {

            var item = {};
            item.Name = 'First';
            item.Id = 123;

            var item2 = {};
            item2.Name = 'Second';
            item2.Id = 123456;
            return {tagList: [item,item2]};
        },

        HandleRemove: function(c){
            console.log('On REMOVE = ', c);
        },

        render: function() {
            return (<div>
                {this.state.tagList.map(function(item){
                    return(
                            <TagItem nameProp={item.Name} idProp={item.Id} key={item.Id} click={this.HandleRemove}/>
                    )}, this)}
                    </div>
            )       
        }

    });
    // CHILD
    var TagItem = React.createClass({

        handleClick: function(nameProp)
        {
            this.props.click(nameProp);
        },


        render: function(){
            return(
                <span className="react-tagsinput-tag" ><span onClick={this.handleClick.bind(this, this.props.nameProp)}>{this.props.nameProp}</span><a className='react-tagsinput-remove' ></a></span>
            )
        }
    }); 

Few changes:

Added 'this' after the tagList mapping. To be honest I am not entirely sure why - perhaps a more experienced programmer can tell us.

Added a key to each TagItem. This is recommended and an the console will inform you that you should do this so that if the state changes, React can track each item accordingly.

The click is passed through the props. See React js - having problems creating a todo list

Community
  • 1
  • 1
AndrewB
  • 11
  • 2
  • Yeah, the same as Felix advised. slightly more detailed. Thanks a lot, will try to understand "this" concept, looks so different from Java... – user2787338 Jul 22 '15 at 15:07
  • No problem. Check out this: https://github.com/getify/You-Dont-Know-JS/blob/master/this%20&%20object%20prototypes/README.md#you-dont-know-js-this--object-prototypes – AndrewB Jul 22 '15 at 15:15