React: neatly make variables available to your event handlers

I keep forgetting to write this down or show it to other programmers. But I write a lot of React code... like TONS of it... and a lot of times I need to deal with events. One common challenge is I have some variable I need to pass along to some event callback.

Example

In this example, we create a simple component that takes a collection of data and turns it into a set of buttons. Since we want to encapsulate the selected button, we track it in the state of the component responsible for rendering the buttons.

But how do we include the target index with the callback?

Typically it looks something like:

class Foo extends Component {
  state = { index: this.props.initialIndex }

  handleButtonClick = (index) => {
    this.setState({ index });
  }

  render () {
    return (
      {this.props.buttons.map((button, index) => (
        <button 
          onClick={() => { this.handleButtonClick(index) }}
        >
          {button.text}
        </button>
      ))}
    );
  }
}

Cleaning this up

Problems with this approach are we now have a new anonymous function defined for every button we generate. Let's not make a deal out of this if you have been coding this up this way - it is O.K we are all human and as humans do - let's explore other ideas.

Try using native API's like data-*

I tried this out, and it works great, let's refactor the component from before just a bit using native API's and patching the fields we are interested in into the synthetic event.

class Foo extends Component {
  state = { index: this.props.initialIndex }

  handleButtonClick = (event) => {
    this.setState({ index: event.currentTarget.dataset.index });
  }

  render () {
    return (
      {this.props.buttons.map((button, index) => (
        <button
          data-index={index}
          onClick={this.handleButtonClick}
        >
          {button.text}
        </button>
      ))}
    );
  }
}

And there it is, I wanted to get this down before I forgot again... but give this a try, or if you have some feedback on this approach let me know.

Until then, thanks for reading and I will see you in the next post!

P.S But that field depth though...

I am looking at you event.currentTarget.dataset.*, we could try destructuring this in the signature, something like:

handleButtonClick = ({ currentTarget: { dataset } }) => {
  this.setState({ index: dataset.index });
}