5

I have a React Component that renders a <ul> and inserts <li> elements based on state.

class SimpleComponent extends React.Component {
  constructor(props) {
    this.state = { menu_items: [name: "First", id: 10] }
    this.clickMenu = this.clickMenu.bind(this)
  }
  generateLinks() {
    let to_return='';
    for (var i=0;i<this.state.menu_items.length; i++) {
      to_return = to_return + `<li><a onclick={clickMenu}> ${this.state.menu_item[i]['name']} </a></li>`
     }
     return to_return;
   }
   clickMenu(e) {
     console.log(e.target)
   }
   render() {
     return(
       <ul dangerouslySetInnerHTML={this.generateLinks()}></ul>
     )
   }
}

When I click on the Anchor, the console indicates Uncaught ReferenceError: clickMenu not defined. I've tried using this.clickMenu instead, but nothing occurs. I've noticed that the rendered anchor looks like:

<a onclick="{menuClick}">

Is there a way to create these anchor elements to have React pick up the onClick definitions rather than passing them to the browser to interpret?

Zach
  • 539
  • 1
  • 4
  • 22
Andy Gauge
  • 1,428
  • 2
  • 14
  • 25
  • Why not take `clickMenu` out of `generateLinks` and make it its own prototype element? Then you can just use it as `this.clickMenu` – Andrew Dec 20 '17 at 00:36
  • I'm trying to generate an achor that calls this.clickMenu when clicked. clickMenu I believe that means it is a prototype element. I can't figure out what exactly a prototype element is though. – Andy Gauge Dec 20 '17 at 00:55
  • Sorry about the confusing verbage. I wanted to be as technical as possible. When I say prototype element I mean "class method". JavaScript doesn't have real classes, so it's fundamentally incorrect to refer to it as such. – Andrew Dec 20 '17 at 00:58

1 Answers1

7

With React you're supposed to build components and subcomponents, not compose long HTML strings. You're basically undermining React's usage pattern, which is precisely the reason why the attribute you're using contains the word "dangerously".

Here's one way how to implement that list:

class SimpleComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { menu_items: [{ name: "First", id: 10 }] };
    this.clickMenu = this.clickMenu.bind(this);
  }
  clickMenu(id) {
    console.log(id);
  }
  render() {
    var items = this.state.menu_items.map(item => {
      let handleClick = () => {
        this.clickMenu(item.id);
      };
      return (
        <li key={item.id}>
          <a onClick={handleClick}>
            {item.name}
          </a>
        </li>
      );
    });
    return <ul>{items}</ul>;
  }
}

I'm building an array of <li> elements by mapping state.menu_items. Since the component is re-rendered when state changes occur, this will always update according to the current state (and only make changes that are actually necessary, which is one of React's defining characteristics).

Also note that your constructor was missing the super(props) call, and your state array's single element wasn't wrapped in curly braces.

  • You might want to use arrow functions in your components methods declarations, which would allow you to remove the need to bind `this` for those methods. e.g : `clickMenu = (id) => {`, and remove the line that goes `this.clickMenu = this.clickMenu.bind(this);`. [Live example here](https://codesandbox.io/s/wvn117o35). The best explanation I have found so far is [in the TypeScript docs](https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript), but it applies to ES6 classes in general, not only to TypeScript (see the "Use Instance Functions" paragraph in the "Fixes" section). – aurelienshz Dec 20 '17 at 01:05
  • @aurelienshz True, but my answer is primarily about implementing a list and less about which context `this` is referring to. I didn't want to turn it into a React primer ;) –  Dec 20 '17 at 01:14
  • 1
    OK, that did work. I do appreciate the effort. Hadn't thought about assembling the response within the render method. Modern JavaScript is increasingly challenging. @aurelienshz earlier today I learned about the bind(this) chain. chris g: I see the typos, this code was extrapolated from the actual code to illustrate the specific challenge I was experiencing. – Andy Gauge Dec 20 '17 at 04:52
  • Your admonition not to build rickety HTML strings was stern but very helpful for me. It's possible to go way off of the ReactJS path and into a rabbit hole of strange click handling patterns. But it's better to stay on the road more travelled. I ended up putting my link into a totally separate React component. – John Gorenfeld Jun 17 '21 at 07:55