0

I have simple hello world code:

import React from 'react';

class ShoppingList extends React.Component {

    getComponent(event) {
        console.log('li item clicked!');
        event.currentTarget.style.backgroundColor = '#ccc';
    }

    render() {
        return (

            <div className="shopping-list">
                <h1>This is props name: {this.props.name}</h1>
                <ul>
                    <li>Item 1</li>
                    <li>Item 2</li>
                    <li>Item 3</li>
                </ul>
                <div>
                    <ul>
                        <li onClick={this.getComponent.bind(this)}>Component 1</li>
                    </ul>
                </div>
            </div>

        );
    }
}

module.exports = ShoppingList;

When I click on <li>Component 1</li> nothing happens.

Why? Page is rendered successfully. No errors,
everything is ok, but handler not working.

FULL EXAMPLE:

Node server: app.js

var express = require('express');
var app = express();

var routes = require('./routes/index')

app.use('/', routes);

app.set('port', process.env.PORT || 9080);

app.use('/public', express.static(__dirname + '/public/'));

app.set('views', __dirname + '/views');
app.set('view engine', 'jsx');
app.engine('jsx', require('express-react-views').createEngine());

var server = app.listen(app.get('port'), function () {
    console.log(__dirname + '/public/');
    console.log('STARTED');
});

route: index.js:

var express = require('express');

var router = express.Router();

router.get('/', function (req, res) {
    res.render('index', {name:"AHOJ"});
});

module.exports = router;

index.jsx:

import React from 'react';


var ShoppingList = require('./components/ShoppingList');

class IndexComponent extends React.Component {
    constructor(props) {
        super(props);
        this.getComponent = this.getComponent.bind(this);
    }

    getComponent(event) {
        console.log('li item clicked!');
        event.currentTarget.style.backgroundColor = '#ccc';
    }
    render() {
        return (

            <DefaultLayout name={this.props.name}>
                <div>
                    <ul>
                        <li onClick={this.getComponent}>Component 1</li>
                    </ul>
                </div>
            </DefaultLayout>

        )
    }
};

module.exports = IndexComponent;

master.jsx:

var React = require('react');

class MasterLayout extends React.Component {
    render() {
        return (
            <html lang="eng">
                <head>
                    <meta charset="utf-8" />

                    <title>{this.props.name}</title>
                    <meta name="description" content="The HTML5 Herald" />
                    <meta name="author" content="SitePoint" />
                    <link rel="stylesheet" type="text/css" href="/public/css/main.css" />
                </head>
                <body>
                    {this.props.children}
                </body>
            </html>
        )
    }
};

module.exports = MasterLayout;

I hope, this code is clear for you, its hello world project. In full example is class ShoppingList : IndexComponent.

I read some tutorials, and I thinks, my code is correct, page is rendered successfully. No errors,
everything is ok, but handler not working.

<li> have not data-reactid

3 Answers3

3

That isn't how you should structure your component. Do it like so:

class ShoppingList extends React.Component {
    constructor(props) {
        super(props);
        this.getComponent = this.getComponent.bind(this);
    }

    getComponent(event) {
        console.log('li item clicked!');
        event.currentTarget.style.backgroundColor = '#ccc';
    }

    render() {
        return (
            <div className="shopping-list">
                <h1>This is props name: {this.props.name}</h1>
                <ul>
                    <li>Item 1</li>
                    <li>Item 2</li>
                    <li>Item 3</li>
                </ul>
                <div>
                    <ul>
                        <li onClick={this.getComponent}>Component 1</li>
                    </ul>
                </div>
            </div>
        );
    }
}

What I have done here is I have created a constructor for your component. The constructor calls super() so that it can use the this keyword.

When used in a constructor, the super keyword appears alone and must be used before the this keyword can be used. This keyword can also be used to call functions on a parent object.

Then, it calls .bind(this) and binds getComponent to the component. Now the onClick handler can be changed to onClick={this.getComponent}. Moving the call to .bind() to the constructor offers a significant performance boost, since now the method is bound only once, instead of being bound over and over every time the component gets rendered.

Extra tip: change the name of getComponent's argument from event to something else. event is not a reserved keyword in JS, but it is however a global in certain versions of IE. Pretty tricky bug to track down.

stelioslogothetis
  • 9,371
  • 3
  • 28
  • 53
  • 3
    Just to point out, the _reason_ to use `bind()` in the constructor is that it's preferable to do it once when the component is initialized, instead of inside of `render()` which will redundantly bind and re-bind the function every time `render` is run. – jered Jul 26 '17 at 17:23
  • Thank you for your reply, still same problem. Not working. Constructor is called successfully, but handler not working, I am missing data-reactid in dom. –  Jul 26 '17 at 17:55
  • @stybl still not working, console is clear without errors and logs. –  Jul 26 '17 at 18:00
  • @praeslai The problem is not related to the component. This code works properly. The problem is somewhere else. Try creating a new react project and adding everything to it bit by bit, testing along the way. When the handler stops working, you'll know what the problem is. – stelioslogothetis Jul 26 '17 at 18:06
  • `this.testMethod = this.test.bind(this);` then call `this.testMethod();` works sucesfully –  Jul 26 '17 at 18:12
1

Just an FYI, you can also use stateless functional components and use a method stored in a constant:

import React from 'react';
import { render } from 'react-dom';

const clickHandler = e => {
  console.log(`${e.target} \n element clicked!!! \n -------`);
};

const App = () => (
  <div>
    <ul>
      <li style={{border:"solid 1px"}}
        onClick={clickHandler}
      >Click Me</li>
    </ul>
    <h2 onClick={clickHandler}>I'm a header</h2>
  </div>
);

render(<App />, document.getElementById('root'));

If you click on the <li> the console will show:

[object HTMLLIElement] 
 element clicked!!! 
 -------

If you click on the header it'll show:

[object HTMLHeadingElement] 
 element clicked!!! 
 -------

But of course if you need to extend the component class you can use it, although is not necessary to create the constructor if you use the target instead of current target:

https://developer.mozilla.org/es/docs/Web/API/Event/currentTarget

Here's the code without the constructor:

import React, {Component} from 'react';
import { render } from 'react-dom';

class App extends Component {

  clickHandler(e){
    console.log(e.target);
    e.target.style.backgroundColor = '#ccc';
  }

  render(){
    return(
      <div>
        <ul>
          <li style={{border:"solid 1px"}}
            onClick={this.clickHandler}
          >Click Me</li>
        </ul>
        <h2 onClick={this.clickHandler}>I'm a header</h2>
      </div>
    );
  }

}

render(<App />, document.getElementById('root'));

As you can see it works in both cases:

https://codesandbox.io/s/nZY3W5x0p

Rodrigo
  • 1,638
  • 1
  • 15
  • 27
0

Change your li to:

<li onClick={() => this.getComponent}>Component 1</li>

This way it will reference it properly when actually clicked, and not when rendered.

Vlatko Vlahek
  • 1,839
  • 15
  • 20
  • 1
    You should take a look at [Why you shouldn't use inline arrow functions in JSX props](https://stackoverflow.com/questions/36677733/why-jsx-props-should-not-use-arrow-functions). It is a bad practice and will cause you problems. Hurt performance. Causes increased work for the garbage collector and breaks `PureComponent` shallow compare. – Kyle Richardson Jul 26 '17 at 17:26
  • (Oops, I missed the inline arrow function--my bad. But this is just a fancy way of doing the `bind` from the OP's source, so makes no difference.) – Dave Newton Jul 26 '17 at 17:28
  • Using an inline arrow function makes a big difference when it comes to performance. – Kyle Richardson Jul 26 '17 at 17:29
  • @KyleRichardson Appreciate the link. – Vlatko Vlahek Jul 26 '17 at 17:29