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.
What functions can you simply or combine in your projects?
While building my React & Redux project, I thought about how I can streamline how many functions and components I was using. An example being the game buttons.
The post Abstracting Button Events in React appeared first on Shannon Crabill — Software Engineer && Email Developer.
Top comments (8)
Hi how would you handle internationalization with this for this for example Ice Cream in german is Eis and in french its Glace i don't know if making the switch logic on hardcoded values a good idea
btw
this
works the same as this:
There are a surprising number of uncapitalized language names, among other grammatical errors, in this callout of the lack of i18n considerations. Also, while "ice" in French is indeed "glace" (not capitalized btw), "ice cream" is actually "crème glacée", and "Eis" (which, you're right, German writers would capitalize) refers just to "ice", not "ice cream".
I know that "glace" in French is not capitalized, I wanted to go with the same format from the buttons.
I wanted to point out that the implementation is pretty fragile if the business decides one day that more languages need to be supported
something like the following would be safe ass the text will most likely be used as a i18n key depending on the library that is used
This way - onClick={this.handleClick} we also can get event object, coz it passes to function automatically. So there's no need for anonymous function. As you may know - the function of that type are bad for performance, the can trigger non expected re-render.
Sorry mate, but I'd rather have a separate function over a cluttered switch statement. Also, you are not reusing anything. You have to to update the switch statement every time you get a new button.
This apology seems rather insincere. If I were making it, I would provide some amount of helpful advice as to ways I saw for the code to improve, like "Instead of a switch statement (which you will need to add or remove cases to whenever the button behavior is updated), you'll likely find brace-notation access with an interpolated string to be a better abstraction.
Despite that, this is very solid, useful information! Sorry for not saying so earlier 😅"
My point was that the article did not convince me to use the listed approach. Going further, I do not recommend it at all, because the extra switch abstraction is pointless, makes code less readable and error-prone in a long run.
I'd argue that the logic of what to do with a specific type of order ("Pizza", "Cheeseburger" and "Ice Cream") shouldn't be the responsibility of the React component. It should be refactored outside of the component so that the component can pass some kind of metadata (the name such as "Pizza" etc) to the part of the system actually responsible for acting based on the business requirements (business logic).
If you want to go a step further into actually implementation details of the above example I'd say that reducers may be a good choice here so that the component dispatches an event and delegates the responsibility of acting upon that event (placing an order) to the reducer.
But... I'd also argue that this is an example and there are always more suitable solutions but which may not bring across the knowledge the way the author meant to.
In the example used I believe that the functions were kept in the same component and invoked the way they are based on a conditional to keep it small and digestible without scrolling from one example to the other to understand the relation between them.