scenario

Imagine that we now have a very, very long array, and we want to loop through that array to create a set of elements, each of which needs to be bound to a click event handler; We need to get the array item information for the currently clicked element in the event handler.

The official operation

The React official document provides a common solution for this scenario:

In the loop, we usually pass additional arguments to the event handler. For example, if the ID is the id of the row you want to delete, you can pass arguments to the event handler in either of the following ways:

<button onClick={(e) = > this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)} >Delete Row</button>
Copy the code

The above two approaches are equivalent and are implemented using the arrow Function and function.prototype.bind, respectively.

In both cases, the React event object e is passed as the second argument. With the arrow function, the event object must be passed explicitly; with bind, the event object and more arguments are passed implicitly.

The problem of official operation

The most obvious problem with the above approach is that it recreates the event handler every time it is rendered, either with the arrow function or with.bind().

The arrow function goes without saying. When you use.bind() in render, React executes the.bind() for each item in the array and uses the result it returns as an event handler for the element. Obviously,.bind() returns a new value each time it is executed (as opposed to bind once in constructor and then directly use the result of bind in render).

If our array has more than 100 events, then we have to recreate more than 100 event handlers every time we render. Worse, if we pass the event handler into the prop loop as a child of the creation, it could result in additional rerendering of those children.

Optimization idea

We want all elements created by the loop to have bound event handlers that point to the same function object and that don’t change every time we render. This requires that we cannot pass information from the loop item directly to the event handler as an input.

When the event handler executes, the information we must get is the event object that is currently firing the event. So instead of explicitly passing in parameters, we can consider getting the information we need from the Event object.

How do I get the information in the Event object that corresponds to the current array entry?

dataset

The htmlElement. dataset attribute allows access to all sets of custom data attributes (data-*) set on elements in HTML or DOM, both in read mode and write mode.

Take a look at the MDN example:

<div id="user" data-id="1234567890" data-user="johndoe" data-date-of-birth>John Doe
</div>

var el = document.querySelector('#user');

// el.id == 'user'
// el.dataset.id === '1234567890'
// el.dataset.user === 'johndoe'
// el.dataset.dateOfBirth === ''

Copy the code

Therefore, we can do this: in Render, add the dataset attribute to each element created by the loop, binding the information of the current array item to it. The binding information is then retrieved from the event.target.dataset in the event handler.

If the array object data is complex, directly binding to the dataset will result in a long string of information attached to the corresponding HTML tag, and all the information is in string format, which needs to be analyzed again. So we can just bind the index of the array entry and fetch the array object from the index in the event handler.

Practical example

import React, { Component } from "react";
class ClassComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      curText: "".toClickList: [{id: 1.text: "I am 1"}, {id: 2.text: "I'm a 2"}, {id: 3.text: "I'm a 3",}]};this.setText = this.setText.bind(this);
  }

  // Event handler
  setText(event) {
    const index = event.target.dataset.index
    const item = this.state.toClickList[index];

    this.setState({
      curText: item.text
    })
  }

  render() {
    const { toClickList } = this.state;
    return (
      <div>{toclickList.map ((item, index) => {return ({/* bind the dataset attribute to each item in the loop. So here we're going to tie index to */}<button onClick={this.setText} key={item.id} data-index={index}>
              {item.text}
            </button>
          );
        })}
        <br/>Current click on text:{this.state.curtext}</div>); }}export default ClassComponent;
Copy the code


conclusion

We through the loop binding for each element in the dataset properties, the corresponding item index index of an array is binding on the DOM, again through the event in the event handler. The target. The dataset. The index to the current event corresponding index of array elements, and then take the object information under the original array.

Not only in the React class component, but in any other scenario where you need to bind event listeners to elements created in a loop, you can use this idea to avoid creating event handlers repeatedly.

Refer to the directory

React event handling

React Dynamic Events