简体   繁体   中英

Handling clicks on dynamically generated buttons

Background

Hello, I am very comfortable with C# and VB.net but I'm new to Javascript and currently trying to learn React as well. I'm working on a simple ticket purchase application for learning purposes. I was able to make this application in Angular in a day, but React has been proving to be more difficult. It is apparent I need to learn JS fundamentals with React, whereas I feel like I didn't need any previous knowledge to pick up Angular.

Issue

I spun up a Create React App project and started tinkering with it (I learn best by tinkering). I'm currently able to generate a button for each Exhibitor (theater chain). These buttons are dynamically generated because eventually, I won't know how many exhibitors there are. After lots of Googling and Stack Overflowing, here's the code I've come up with (please forgive any noob syntax/formatting):

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class TicketOrder extends Component {
  constructor () {
    super();
    
    this.handleClick = this.handleClick.bind(this);
    
    // List of Exhibitors
    this.exhibList = {
      TestA     : "abc",
      TestB     : "bcd",
      TestC     : "cde"
    };
  }
  
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Ticketz</h2>
          <p id="userGuide">Hello. Please select an Exhibitor:<br/><br/> </p>
        </div>
        <br/>
        <div>
        {
          Object.keys(this.exhibList).map(function(key) {
            return (<p><button className="button" onClick={key => this.handleClick}>{key}</button></p>);
          })
        }
        </div>
    </div>
    );
  }
  
  handleClick () {
    console.log("Clicked!");
  };
}

export default TicketOrder;

I presume I'm making some fundamental mistakes and I admit I don't fully grasp the React concepts yet, so please go easy on me! The error I'm getting no matter what I try is:

"Uncaught TypeError: Cannot read property 'handleClick' of undefined".

I've tried many variations of the above code to no luck. Would anyone be so kind as to tell me what's wrong with my code/concept?

Once this issue is resolved, I still have an underlying issue that I haven't been able to conceptualize yet which is having handleClick know which button was pressed. For example, if the user pressed TestA, console.log("abc") . Eventually, once I understand more about JS and React, I'm guessing I'd be changing the state of the component in handleClick based on the button pressed. Any insight on how to accomplish this would be super appreciated.

The "this" you're trying to access is not accessible, because you're in the function scope. To avoid it you can use the arrow function syntax. Also, the map function is iterating on your array of keys, so you just need to give the handleClick function this key as an argument. Your mapping would like like this:

 {
    Object.keys(this.exhibList).map((item, index) => {
       return (<p><button key={index} className="button" onClick={() => this.handleClick(item)}>{item}</button></p>);
    })
 }

In the arrow function {}, "this" will be the one you want to access (aka TicketOrder). Also remember to give a unique key property to any element you map (I put the index in the previous example), this helps React algorithm know which elements changed. Cf here

Your handleClick would look like this:

handleClick = (message) => {
  console.log("Clicked!", message);
};

You can see that a different key is passed as an argument and displayed depending on which button the user clicks on.

That's how the whole thing would look for me:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class TicketOrder extends Component {
  // Drop the constructor you don't need it anymore, especially with Create React App
  exhibList = {
    TestA       : "abc",
    TestB       : "bcd",
    TestC       : "cde",
  }

  handleClick = (key) => {
    console.log("Clicked!", key);
  };

  render() {
    return (
      <div className="App">
          <div className="App-header">
              <img src={logo} className="App-logo" alt="logo" />
              <h2>Ticketz</h2>
              <p id="userGuide">Hello. Please select an Exhibitor:<br/><br/> </p>
          </div>
          <br/>
          <div>
          {
              Object.keys(this.exhibList).map((item, index) => {
                  return (<p><button key={index} className="button" onClick={() => this.handleClick(item)}>{item}</button></p>);
              })
          }
          </div>
     </div>
    );
  }
}

export default TicketOrder;

Change your onClick to this:

onClick={this.handleClick.bind(this, key)}

and your handleClick to:

 handleClick(ctx) {
   console.log(ctx, "Clicked!");
 }

Change your code like this:

onClick={() => this.handleClick(key)}

handleClick(key) {
  console.log('Clicked!', key)
}

and if you want the event:

onClick={event => this.handleClick(event, key)}

handleClick(event, key) {
  console.log('Clicked!', event, key)
}

Hope that helps! :)

Your map function has no idea about this inside, hence Cannot read property 'handleClick' of undefined" . That means you're trying to invoke undefined.handleClick, in that case this is undefined.

You can use fat arrow function () => {} which bind's this to the function out of the box. To do so change your map function to

map((key, index) => {
  return (
     <p key={index}>
     <button
       className="button" 
       onClick={() => this.handleClick(key)}>{key}
     </button>
     </p>);
})

Also note the index argument, each child in iterator should have unique key.

Then your handleClick:

handleClick(key){
   console.log('Clicked',key)
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM