Jack The Baker
Jack The Baker

Reputation: 1903

ReactJS onClick handler in map return undefined

I want to add a handler on <a> in map:

My simple functions:

getFacilities = (e) => {
    console.log(e)
}

In render:

    const items = this.state.data;
    let result = items.map(function(value, i){
        return (
            <List.Item key={i}>
               <a onClick={this.getFacilities}>{value.user.name}</a>
            </List.Item>
        );
    });

TypeError: Cannot read property 'getFacilities' of undefined

There are many topics with this error, Almost all of them use this as solution:

I add this in constructor

this.getFacilities = this.getFacilities.bind(this)

But it not working in my case and still return same error. it's simple, confused can not figure out what I missed I done wrong.

Upvotes: 0

Views: 95

Answers (5)

Andy
Andy

Reputation: 63589

The key thing to note is the difference between normal function expressions and the new ES6 arrow functions. In short: unlike function expressions arrow functions do not maintain their own this (or arguments) - they borrow it from the outer lexical environment.

"An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords. Arrow function expressions are ill suited as methods, and they cannot be used as constructors."

So when you refer to this in that map callback this doesn't refer to the class.

So 1) change the function expression to an arrow function:

let result = items.map((value, i) => {
  return (
    <List.Item key={i}>
      <a onClick={this.getFacilities}>{value.user.name}</a>
    </List.Item>
  );
});

2) Since you're using an arrow function for your class method you can remove as this would only be required if you were using a standard class method definition rather than a class field.

this.getFacilities = this.getFacilities.bind(this);

However there's some evidence to suggest that class fields (using arrow functions) such as these are worse than using standard class method format, so you may be better off keeping the binding in the constructor and using the standard format for functions that you're passing around:

getFacilities(e) {
  console.log(e)
}

It's something to bear in mind.


PS, from that link:

"It is unnecessary to do that to every function. This is just as bad as autobinding (on a class). You only need to bind functions that you pass around. e.g. onClick={this.doSomething}. Or fetch.then(this.handleDone) — Dan Abramov‏"

Upvotes: 1

Jay
Jay

Reputation: 3117

Please dont use Arrow function

class Name extends Component {
  constructor(props) {
    super(props);
    this.getFacilities = this.getFacilities.bind(this);
  }

  getFacilities(e) {
    // remove Arrow function
    console.log(e);
  }

  render() {
    return (
      <div>
        {this.state.data.map(function(value, i) {
          return (
            <List.Item key={i}>
              <a onClick={this.getFacilities}>{value.user.name}</a>
            </List.Item>
          );
        })}
      </div>
    );
  }
}

Upvotes: 0

tolotra
tolotra

Reputation: 3270

You need to change map predicate to arrow function (to bind the this context to your component not to the map function)

 let result = items.map((value, i) => {
        return (
            <List.Item key={i}>
               <a onClick={this.getFacilities}>{value.user.name}</a>
            </List.Item>
        );
    });

After that, you need to change the getFacilities declaration

Solution 1 - Remove this line from constructor

this.getFacilities = this.getFacilities.bind(this)

Solution 2 - But if you want, you can change getFacilitiesto hoisting function

getFacilities(e) {
    console.log(e)
}

And keep this line in constructor

this.getFacilities = this.getFacilities.bind(this)

Upvotes: -1

Clarity
Clarity

Reputation: 10873

To ensure proper binding of this, use arrow function in the map callback:

  let result = items.map((value, i) => {
        return (
            <List.Item key={i}>
               <a onClick={this.getFacilities}>{value.user.name}</a>
            </List.Item>
        );
    });

Upvotes: 1

noodles_ftw
noodles_ftw

Reputation: 1313

Because this inside your map function is not defined. Use an arrow function to bind the map function to the parent scope:

const items = this.state.data;
let result = items.map((value, i) => {
    return (
        <List.Item key={i}>
           <a onClick={this.getFacilities}>{value.user.name}</a>
        </List.Item>
    );
});

Upvotes: 1

Related Questions