1

I'm so sorry for how badly worded that title is but I'm just learning React and I got something working in what feels like a bit of a hacky way, and I'm trying to figure it out properly.

Here's the code that I initially couldn't get to work (the removeItem part was throwing an error as this.removeItem was undefined).

render() {
    return (
      <div className="App">
        <AddItem addItem={this.addItem.bind(this)} />
        <ul>
          {this.state.listItems.map(function(item,index){
              return (
                  <ListItem id={index} key={index} title={item} removeItem={this.removeItem.bind(this)} />
              );
          })}
        </ul>
      </div>
    );
  }

I realised that it was something to do with scope, in that "this" inside map() was not referring to the same "this" that addItem is. The solution I came up with was just to define "this" in a separate variable for use inside the map().

render() {
    var theApp = this;
    return (
      <div className="App">
        <AddItem addItem={this.addItem.bind(this)} />
        <ul>
          {this.state.listItems.map(function(item,index){
              return (
                  <ListItem id={index} key={index} title={item} removeItem={theApp.removeItem.bind(theApp)} />
              );
          })}
        </ul>
      </div>
    );
  }

My query is - I don't feel like I've seen this kind of approach in other peoples' code or tutorials or such, but I can't quite get my head round what's going on and what would be a better way of handling this. Any thoughts, or even pointers on what to Google much appreciated!

Brandon
  • 38,310
  • 8
  • 82
  • 87
MyNotes
  • 426
  • 4
  • 11

3 Answers3

1

This is called context binding, you need to bind the proper context otherwise this keyword (points to react component) will not be available inside map. You can write it like these way:

1. By using arrow function:

{   
    this.state.listItems.map( (item,index) => {
        return (
            <ListItem id={index} key={index} title={item} removeItem={this.removeItem.bind(this)} />
        );
    })
}

2. By using .bind(this) with callback method:

{   
    this.state.listItems.map( function(item,index) {
        return (
            <ListItem id={index} key={index} title={item} removeItem={this.removeItem.bind(this)} />
        );
    }.bind(this))
}

Suggestion: Instead of binding the events inside render method, you should bind them in the constructor, it will avoid the creation of new function during the re-rendering.

Reference: https://stackoverflow.com/a/31296221/5185595

For more detail on arrow function check this answer: https://stackoverflow.com/a/34361380/5185595

Community
  • 1
  • 1
Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
0

You're mostly correct. However the word you're looking for is "context", not "scope".

Scope is how visible a variable declaration is to other code. A variable is either in the global scope, or it's scoped locally to the function in which it was called.

Context deals specifically with the value of the keyword this. this refers to the object that the function was called within. In React, since all of the functions are called from within the Component object, the value of this is typically the Component itself.

On to your workaround to the problem - it's perfectly valid and is in fact used quite often by developers. You'll often see something like var self = this so that the original value of this can easily be retained.

However, if you're coding in ES6 or using Babel or some other transpiler, you can use the ES6 "arrow function":

render() {
    return (
      <div className="App">
        <AddItem addItem={this.addItem.bind(this)} />
        <ul>
          {this.state.listItems.map((item,index) => {
              return (
                  <ListItem id={index} key={index} title={item} removeItem={this.removeItem.bind(this)} />
              );
          })}
        </ul>
      </div>
    );
  }

The "arrow function" does not bind a new value for this despite being a new function call. Instead it uses the this value of the context it was called in. It's very handy for situations like this; myself and other developers use it all the time, so it's quite in style.

jered
  • 11,220
  • 2
  • 23
  • 34
  • Noting that in general you don't want to `bind` within `render` methods; handlers should either be bound in constructors or created via class properties. – Dave Newton Apr 10 '17 at 19:34
0

you want to keep the class context inside your map function. To make things more performant you should use a function that is pre-bound to the class in the return of your map.

removeItem = () => {
    ...
}
listItem = (item, index) => {
    return (
        <ListItem id={index} key={index} title={item} removeItem={this.removeItem} />
    );
}
getListItems = () => {
    return this.state.listItems.map((item,index) => this.listItem(item, index))
}
render() {
    const elems = this.getListItems();
    return (
      <div className="App">
        <AddItem addItem={this.addItem.bind(this)} />
        <ul>
          {elems}
        </ul>
      </div>
    );
  }

notice I use an arrow function on your class methods. this way each of those methods are pre-bound. you no longer need to call .bind(this) everywhere and its more performant

The arrow inline binding is available if you have enabled stage-2 or transform-class-properties in babel. If you haven't you can pre bind your functions in the constructor so that binding only needs to happen once.

constructor() {
    super();
    ...
    this.removeItem = this.removeItem.bind(this);
    this.listItem = this.listItem.bind(this);
    this.getListItems = this.getListItems.bind(this);
}
John Ruddell
  • 25,283
  • 6
  • 57
  • 86