< Previous: Using JSX to Render Several Elements at Once   Next: State vs Props in React >

Handling Events with JSX: onClick

The onClick event has been around for a long time, and it lets developers call a particular piece of code when the user clicks or taps a particular item. You can use this in React too, but the syntax is a little different. To extend our current code, we might want to add a button that shows a different random name and country every time it's clicked.

Thanks mainly to some historical quirks of JavaScript, there are three primary ways of calling methods in React/ES6, of which the first two are by first the most common. Here's the most basic example:

src/pages/Detail.js

class Detail extends React.Component {
    buttonClicked() {
        console.log('Button was clicked!')
    }

    render() {
        return (<div>
            <p>Hello, {chance.first()}.</p>
            <p>You're from {chance.country({ full: true })}.</p>
            <button onClick={this.buttonClicked}>Meet Someone New</button>
        </div>);
    }
}

export default Detail;

There are three new things there:

  • The <button> JSX element is identical to the <button> HTML element: it draws a clickable button on the screen, in this case saying "Meet Someone New".
  • onClick={this.buttonClicked} is how you attach events to JSX elements. You shouldn't put quotes around it like you would have done with HTML – this is code being called, after all.
  • There's a new buttonClicked() method that writes some text to your browser's console window. It uses this to mean "this is my method as opposed to someone else's."

Save that code and try clicking the button – you should be able to see the message being printed out. So, that's the first way to call methods when events occur, and I hope you'll agree it's easy.

The second way is where the JavaScript quirks come in. We don't just want to write some text to the debug console, we want to re-render our component so that a new name and place are generated. React components have a special (if rarely used) method baked in to do just that, and it's called forceUpdate(). So, you might think of writing code like this:

<button onClick={this.forceUpdate}>Meet Someone New</button>

Sadly that doesn't work. And it doesn't work because JavaScript gets confused what you mean by "this". Well, it isn't confused, but everyone else certainly is. So while the render() method knows that this refers to the current instance of the Detail component, the code inside the forceUpdate() method won't know, and you'll get errors.

Yes, this is confusing. Yes, it's unpleasant. And yes, you're stuck with it. Fortunately there's a relatively simple solution: a special method called bind() that lets you force JavaScript to use a definition of this you specify. That is, you literally say, "when you see 'this', I mean X, not your own crazy idea of what it might be."

To use bind() just put it after the method name you want to call, then make sure and pass in the current value of this to make that the one used inside your method. Don't worry if this is confusing: this is a JavaScript problem, not a you problem.

To solve this problem once and for all, we need to call buttonClicked() using bind(this), then we can safely call forceUpdate() from inside buttonClicked().

First things first, here's the new button code:

src/pages/Detail.js

<button onClick={this.buttonClicked.bind(this)}>Meet Someone New</button>

Now here's the new buttonClicked() method:

src/pages/Detail.js

buttonClicked() {
    this.forceUpdate();
}

Save those changes, then try clicking your button – easy, huh?

One last thing before we continue – AND THIS NEEDS A BIG WARNING IN CAPITAL LETTERS – you must be careful not to write this by accident:

<button onClick={this.buttonClicked(this)}>Meet Someone New</button>

Notice how that's missing the .bind part? It's a mistake you will make sooner or later, so I want to explain briefly why it's a mistake.

The difference is this: if you write onClick={this.buttonClicked(this)} that code gets called immediately when your page is run, not when the button is clicked. And because that method calls forceUpdate(), it means that render() gets called again, so onClick={this.buttonClicked(this)} gets called again, so forceUpdate() gets called again… ad infinitum – or at least until your web browser gives up, which is probably about a thousand or so times through that loop.

So: if an event is triggered and you need to use this inside the method to handle that event, you need to use bind() to make sure this is what you think it is, and also to ensure the code is not called straight away.

Buy the book for $10

Get the complete, unabridged Hacking with React e-book and take your learning to the next level - includes a 45-day no questions asked money back guarantee!

If this was helpful, please take a moment to tell others about Hacking with React by tweeting about it!

< Previous: Using JSX to Render Several Elements at Once   Next: State vs Props in React >

Copyright ©2016 Paul Hudson. Follow me: @twostraws.