React Question Help

Hi all.

I’m learning React and trying to reinforce some of the basic concepts that I’ve seen but I’m running into a number of issues. I’m wondering if someone could help me understand why my code is not working. I am trying to generate a list using the map function and make it so that each item, when clicked, will console log what was clicked. Everything is working fine, but when I add the ‘onClick’ piece to the li that is being returned, the code breaks with an error of ‘Cannot read property of ‘showLanguage’ of undefined’.

class MyApp extends React.Component {
  constructor(props) {
    super(props);
    this.showLanguage = this.showLanguage.bind(this);
  }
  
  showLanguage(lang) {
    console.log(lang);
  }
  
  render() {
    var languages = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'];
    return (
      <div>
        <ul className='languages'>
          {languages.map(function(lang) {
            return (
              <li onClick={this.showLanguage}>{lang}</li>
            )
          })}
        </ul>
      </div>
    )
  }
}

Use

      {languages.map(function(lang) {
        return (
          <li onClick={this.showLanguage}>{lang}</li>
        )
      }, this)}

or

      {languages.map((lang) => {
        return (
          <li onClick={this.showLanguage}>{lang}</li>
        )
      })}
1 Like

The problem here is the use of the ‘this’ keyword, which seems to be an extremely common pain point with React (in my limited experience). Basically, the ‘this’ being used in the onClick event is referring to the function it sits inside (function(lang) {… etc) rather than MyApp i.e. what you want is MyApp’s showLanguage method, not function (lang)'s showLanguage method (which obviously doesn’t exist and is why you’re getting the error).

So how do you make ‘this’ the ‘this’ that you want? One way is by taking into account that the map function has an optional second argument, which determines what ‘this’ will be when executing the callback function. So, what you can do is add ‘this’ as a second argument, like so:

{languages.map(function(lang) {
  return (
    <li onClick={this.showLanguage}>{lang}</li>
}, this)}

OR better yet use ES6’s fat arrow function which does this automatically, like so:

{languages.map(lang => {
  return (
    <li onClick={this.showLanguage}>{lang}</li>
})}

Using either method will make the ‘this’ INSIDE the function the same as the ‘this’ OUTSIDE of it (i.e. ‘this’ refers to the MyApp class).

Still, there’s one thing left to do to make this work. You need to pass an argument to this.showLanguage! It turns out there are more things to consider about all the different ‘this’s’. I will refer you to the documentation for the how and why of handling events in React.

Essentially, you should always be using ES6 syntax when using React so you don’t have a million ‘this’s’ and ‘binds’ all over your code. I believe the following code should do what you want:

class MyApp extends React.Component {
  constructor(props) {
    super(props);
    this.showLanguage = this.showLanguage.bind(this);
  }
  
  showLanguage(lang) {
    console.log(lang);
  }
  
  render() {
    var languages = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'];
    return (
      <div>
        <ul className='languages'>
          {languages.map(lang => {
            return (
              <li onClick={() => this.showLanguage(lang)}>{lang}</li>
            )
          })}
        </ul>
      </div>
    )
  }
}
2 Likes

Thank you very much for the detailed explanation. I find the concept of binding ‘this’ to be very confusing, even though I’ve heard it explained a number of times.