daniel blythe
daniel blythe

Reputation: 1048

Click handler inside loop (two returns) not working (React JS)

I have a pretty simple piece of React that loos over an array and outputs tab names. However my click handler no longer works (it worked before when I didn't have the loop).

The difference between this piece and before I had the .map loop is that this new piece has two returns in the render function. One for the outer div element that React requires, then one for the looping over the objects.

Does anyone how I can successfully get the click handler working again please?

Please see my component

class TabMenu extends React.Component {
constructor(props) {
    super(props);
    this.state = {

    };
    this.tabMenuList = [
      {
        title: 'My Account',
        section: 'MyAccount'
      },
      {
        title: 'Conference Details',
        section: 'MyAccount'
      },
      {
        title: 'My Abstract',
        section: 'MyAbstract'
      }
    ];
}
handleClick(e){
  e.preventDefault();

  console.log('this is the click handler', this);
  ReactDOM.render(<Conference />,document.getElementById('content'));
}
render() {

  return (
    <div>
      {this.tabMenuList.map(function(menuItem, index){
        return(
          <li data={menuItem.section}>
            <a onClick={this.handleClick.bind(this)} href={'#'}>
              <img src={'assets/img/mail_icon.jpg'} />
              <span>{menuItem.title}</span>
            </a>
          </li>
        )
      })}
    </div>
  );
}
}

Upvotes: 1

Views: 1999

Answers (2)

Dan
Dan

Reputation: 8784

Solution

Use an ES6 arrow function like so:

class TabMenu extends React.Component {
constructor(props) {
    super(props);
    this.state = {

    };
    this.tabMenuList = [
      {
        title: 'My Account',
        section: 'MyAccount'
      },
      {
        title: 'Conference Details',
        section: 'MyAccount'
      },
      {
        title: 'My Abstract',
        section: 'MyAbstract'
      }
    ];
}
handleClick(e){
  e.preventDefault();

  console.log('this is the click handler', this);
  ReactDOM.render(<Conference />,document.getElementById('content'));
}
render() {

  return (
    <div>
      {this.tabMenuList.map((menuItem, index) => {
        return(
          <li data={menuItem.section}>
            <a onClick={this.handleClick.bind(this)} href={'#'}>
              <img src={'assets/img/mail_icon.jpg'} />
              <span>{menuItem.title}</span>
            </a>
          </li>
        )
      })}
    </div>
  );
}
}

Why?

In your React code, this is not referencing TabMenu.

When declaring this within a function, it automatically defaults to the global object - Window in the case of your environment.

Since the following code is not in strict mode, and because the value of this is not set by the call, this will default to the global object.

However, it's important to know that

In strict mode, however, the value of this remains at whatever it was set to when entering the execution context, so, in the following case, this will default to undefined.

Why? Because according to this question and the first answer, ES6 modules use strict by default and thus this within your function equals undefined.

Therefore,

In arrow functions, this is set lexically, i.e. it's set to the value of the enclosing execution context's this. In global code, it will be set to the global object

Your enclosing execution context is TabMenu.

MDN have a great article on this and how it varies depending on the context in which this is called in.

https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/this

Upvotes: 2

Naveed Aheer
Naveed Aheer

Reputation: 1825

Try this

<li data={menuItem.section}>
            <a onClick={this.handleClick.bind(this)} href={'#'}>
              <img src={'assets/img/mail_icon.jpg'} />
              <span>{menuItem.title}</span>
            </a>
          </li>

instead of

<li data={menuItem.section}>
            <a onClick=onClick={this.handleClick.bind(this)} href={'#'}>
              <img src={'assets/img/mail_icon.jpg'} />
              <span>{menuItem.title}</span>
            </a>
          </li>

or you can remove second return may be it will work

Upvotes: 0

Related Questions