Abstracting Click Events in React

An essential part of programming is having clean and simplified code.

A function should do one thing, like update a follower count or submit a form, as opposed to multiple things at once. At the same time, if a function can be reused to perform similar actions based on what input or arguments it receives, it should do that.

As an example, let's say we have three buttons: "Pizza", "Cheeseburger" and "Ice Cream". We're feeling snackish, so let's assume that clicking a button, starts the order for that item.

If each Button was a component in React, the return statement of that component may look like this:

<button onClick={this.handleClick}>
  {this.props.text} // this.props.text = "Pizza", "Cheeseburger" or "Ice Cream"
</button>

Which when a button is clicked, it runs a handleClick function.

handleClick = () => {
  this.props.orderFood(); // This could be anything
}

While I could give each Button instance it's own handleClick function—handlePizzaOrder, handleCheeseBurgerOrder and handleIceCreamOrder—that is creating a lot of work, for functions that do very similar things: Order an item.

What we can do, is take what we know about the Button that was clicked and pass that information to a generalized or abstracted handleClick function. Then, based on that information, run a specific order function.

Abstracting onClick

To start, my Button component looked like this:

<button onClick={this.handleClick}>
  {this.props.text} // this.props.text = "Pizza", "Cheeseburger" or "Ice Cream"
</button>

This means that when any of the three buttons are clicked, the handleClick function starts running. At this point, the handleClick function does not know which of the buttons was clicked. For this to happen, the handleClick function needs to take in the event target.

To do that, we'll change the onClick props to become an anonymous function that takes in the event.

The button goes from this:

<button onClick={this.handleClick}>
  {this.props.text} // this.props.text = "Pizza", "Cheeseburger" or "Ice Cream"
</button>

To this:

<button onClick={(e) => this.handleClick(e)}>
  {this.props.text} // this.props.text = "Pizza", "Cheeseburger" or "Ice Cream"
</button>

And the handleClick function is updated to accept the event as an argument.

handleClick = (e) => {
  // We'll update the rest of the function in the next step
}

Running a Function, Based on the Event Target

Instead of an if/else statement, we can use a switch statement that looks at the innerText of the event target, and based on that, will fire a specific function.

For example, when the "Pizza" button is clicked, we want to start the pizza ordering process of picking a size and toppings. For "Cheeseburger" a number of burger patties, how they should be cooked, and toppings.

Here's our handleClick function and switch case:

handleClick = (e) => {
 switch (e.target.innerText) {
  case "Pizza":
    this.props.orderPizza();
    break;
  case "Cheeseburger":
    this.props.orderCheeseBurger();
    break;
  case "Ice Cream":
    this.props.orderIceCream();
    break;
  default:
    console.log("I'm sorry. We don't have that menu item.")
 }
}

Now let's walk through what is happening.

If the “Pizza” button is clicked, an event object is passed to the function. It has a target property that returns this.

<button>Pizza</button>

From there, we can get innerText which has a value of “Pizza”. Since that meets the first switch case, the orderPizza function is run. If the “Cheeseburger” button was clicked, the second case is fired, and so on.

If there was a fourth button that did not match any of the cases we have specified above, the default case is hit, which is our instance, prints a response to the console and exits the function.

Conclusion

With this setup, I can change what each switch case does, or add new ones without having to change the functionality of the buttons themselves. The return of the Button component stays minimal which makes it easy to read and maintain.

This example is in the context of React, but the principle behind abstracting and keeping components small and easy to maintain can apply to other programming languages.