React and React Native
上QQ阅读APP看书,第一时间看更新

Declaring event handlers

The differentiating factor with event handling in React components is that it's declarative. Contrast this with something like jQuery, where you have to write imperative code that selects the relevant DOM elements and attaches event handler functions to them.

The advantage with the declarative approach of event handlers in JSX markup is that they're part of the UI structure. Not having to track down code that assigns event handlers is mentally liberating.

In this section, we'll write a basic event handler, so you can get a feel for the declarative event handling syntax found in React applications. Then, we'll look at using generic event handler functions.

Declaring handler functions

Let's take a look at a basic component that declares an event handler for the click event of an element:

import React, { Component } from 'react'; 
 
export default class MyButton extends Component { 
 
  // The click event handler, there's nothing much 
  // happening here other than a log of the event. 
  onClick() { 
    console.log('clicked'); 
  } 
 
  // Renders a "<button>" element with the "onClick" 
  // event handler set to the "onClick()" method of 
  // this component. 
  render() { 
    return ( 
      <button onClick={this.onClick}> 
        {this.props.children} 
      </button> 
    ); 
  } 
} 

As you can see, the event handler function, this.onClick(), is passed to the onClick property of the <button> element. By looking at this markup, it's clear what code is going to run when the button is clicked.

See the official React documentation for the full list of supported event property names: https://facebook.github.io/react/docs/.

Multiple event handlers

What I really like about the declarative event handler syntax in JSX is that it's easy to read when there's more than one handler assigned to an element. Sometimes, for example, there are two or three handlers for an element. Imperative code is difficult to work with for a single event handler, let alone several of them. When an element needs more handlers, it's just another JSX attribute. This scales well from a code maintainability perspective:

import React, { Component } from 'react'; 
 
export default class MyInput extends Component { 
 
  // Triggered when the value of the text input changes... 
  onChange() { 
    console.log('changed'); 
  } 
 
  // Triggered when the text input loses focus... 
  onBlur() { 
    console.log('blured'); 
  } 
 
  // JSX elements can have as many event handler 
  // properties as necessary. 
  render() { 
    return ( 
      <input 
        onChange={this.onChange} 
        onBlur={this.onBlur} 
      /> 
    ); 
  } 
} 

This <input> element could have several more event handlers, and the code would be just as readable.

As you keep adding more event handlers to your components, you'll notice that a lot of them do the same thing. It's time to start thinking about how to share generic handler functions across components.

Importing generic handlers

Any React application is likely going to have the same event handling functionality for different components. For example, in response to a button click, the component should sort a list of items. It's these types of super generic behaviors that belong in their own modules so that several components can share them. Let's implement a component that uses a generic event handler function:

import React, { Component } from 'react'; 
 
// Import the generic event handler that 
// manipulates the state of a component. 
import reverse from './reverse'; 
 
export default class MyList extends Component { 
  state = { 
    items: ['Angular', 'Ember', 'React'], 
  } 
 
  // Makes the generic function specific 
  // to this component by calling "bind(this)". 
  onReverseClick = reverse.bind(this) 
 
  render() { 
    const { 
      state: { 
        items, 
      }, 
      onReverseClick, 
    } = this; 
 
    return ( 
      <section> 
        { /* Now we can attach the "onReverseClick" handler 
             to the button, and the generic function will 
             work with this component's state. */} 
        <button onClick={onReverseClick}>Reverse</button> 
        <ul> 
          {items.map((v, i) => ( 
            <li key={i}>{v}</li> 
          ))} 
        </ul> 
      </section> 
    ); 
  } 
} 

Let's walk through what's going on here, starting with the imports. We're importing a function called reverse(). This is the generic event handler function that we're using with our <button> element. When it's clicked, we want the list to reverse its order.

You can see that we're creating an onReverseClick property in this class. This is created using bind() to bind the context of the generic function to this component instance.

Finally, looking that the JSX markup, you can see that the onReverseClick() function is used as the handler for the button click.

So how does this work, exactly? We have a generic function that somehow changes the state of this component because we bound a context to it? Well, pretty much, yes, that's it. Let's look at the generic function implementation now:

// Exports a generic function that changes the 
// state of a component, causing it to re-render 
// itself.
export default function reverse() { 
  this.setState(this.state.items.reverse()); 
} 

Pretty simple! Obviously, this function depends on a this.state property and an items array within the state. However, this is simple to do. The key is that the state that this function works with are generic; an application could have many components with an items array in its state.

Here's what our rendered list looks like:

As expected, clicking the button causes the list to sort, using our generic reverse() event handler:

Now we'll take a deeper look at binding the context of event handler functions, as well as binding their parameters.