Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React OnClick for item not working

I have a react component which renders a list item with individual OnClick.

In order to find out which item was clicked, the handler accepts a parameter. The handler does get invoked - but no matter which item is clicked - console always logs item3 (as if item3 is clicked). What am I doing wrong here?

class Item {
    constructor(props) {
        super(props);
        this.onItemClickHandler = this.onItemClickHandler.bind(this)
    }

    onItemClickHandler (itemName) {
        console.log("Clicked " + itemName)
    }

    render() {
        this.items = ["item1", "item2", "item3"]
        var lis = []
        for (var liName in this.items) {
            var liName2 = this.items[liName]
            console.log("Adding " + this.items[liName])
            lis.push(<li className="item-ListItem" key={this.items[liName]} onClick={() => this.onItemClickHandler(this.items[liName])}><span><a href="#">{this.items[liName]}</a></span></li>)
        }

        return (
          <div className="item">
            <label className="item-Header"><u>items</u></label>
            <ul className="item-List"> 
            {lis}
            </ul>

          </div>
        );
    }

This line:

onClick={() => this.onItemClickHandler(this.items[liName])}>

appears to be correct.

like image 515
justanothercoder Avatar asked Jan 29 '23 02:01

justanothercoder


2 Answers

The issue is that you are not capturing the value of this.items[liName] correctly because by the time you reach the third item iteration the onClick handler will always have the value of this.items[liName] set to the third item.

The solution for that is using closure to capture the value correctly, i edited your code and created a fully working example in this link

https://codesandbox.io/s/3xrp6k9yvp

Also the example code is written below with the solution

class App extends Component {
  constructor(props) {
    super(props);
    this.onItemClickHandler = this.onItemClickHandler.bind(this);
  }

  onItemClickHandler(itemName) {
    console.log("Clicked " + itemName);
  }

  render() {
    this.items = ["item1", "item2", "item3"];
    var lis = [];
    for (var liName in this.items) {
      var liName2 = this.items[liName];
      console.log("Adding " + this.items[liName]);

      //the clickHandler function here is the solution we created a function that get executed immediately each iteration and return a new function that has the correct value of `this.items[liName]` saved
      var clickHandler = (item => {
        return event => {
          this.onItemClickHandler(item);
        };
      })(this.items[liName]);

      lis.push(
        <li
          className="item-ListItem"
          key={this.items[liName]}
          onClick={clickHandler} // here we use the clickHandler function directly
        >
          <span>
            <a href="#">{this.items[liName]}</a>
          </span>
        </li>
      );
    }

    return (
      <div className="item">
        <label className="item-Header">
          <u>items</u>
        </label>
        <ul className="item-List">{lis}</ul>
      </div>
    );
  }
}

For more info and examples about closures check this link


Edit We can use let in ES6 instead of var in our for loop as mentioned by @ArchNoob because using let will make the liName block scoped

like image 67
Amr Labib Avatar answered Feb 03 '23 06:02

Amr Labib


Please take care of indentation while posting the code. Its very difficult to understand without that. You have to make use of closure. whenever the loop gets over liName variable gets set to last index as scope chain will keep the liName value to last one. The solution is not make a new scope between handler and click handler function where it is callled. Here is the solution:

class Test extends React.Component {
  constructor(props) {
    super(props)
    this.onItemClickHandler =             
    this.onItemClickHandler.bind(this)
  }

  onItemClickHandler(itemName) {
    debugger
   console.log("Clicked " + itemName)
  }
  
  render() {
    this.items = ["item1", "item2", "item3"]
    var lis = []
    for (var liName in this.items) {
      var liName2 = this.items[liName]
      console.log("Adding " + this.items[liName])
      debugger
      lis.push( <li className = "item-ListItem"
                  key = {
                    this.items[liName]
                  }
                  onClick = {
                    ((item) => {
                      return () => this.onItemClickHandler(item)
                    })(this.items[liName])
                  }
                 >
                  <span>
                    <a href = "#"> {this.items[liName]} </a>
                  </span> 
                </li>
      )
    }
    
    return (
      <div>
        <div className="item">
        <label className="item-Header">
         <u>items</u>
        </label>
        <ul className="item-List" >
          {lis}
        </ul>
      </div>
      </div>
    )
  }
}

ReactDOM.render(<Test />, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
like image 31
simbathesailor Avatar answered Feb 03 '23 07:02

simbathesailor