October 3, 2022

Robotic Notes

All technology News

3 Ways of Passing Multiple Parameters to the onClick Handler in React

7 min read


While everyone who’s worked on a web development project in the past has had to deal with the onClick event (whether it was using React, Vue or even Vanilla JS) going beyond the basic functionality of calling a simple function to deal with the event object is not that trivial.

In this example-based article I want to quickly cover different ways to send multiple parameters to your event handling function without having to sell your soul to Satan.

The example use case

Let’s take a quick look at a fake use case so we’re all on the same page. Pretend you have a list of items on a fake checkout cart, each item has many properties and upon clicking one of the buttons, you need to delete it from the cart. The problem though, is that deleting it is not that trivial. Before you take it out of the list, you first need to:

  • Make sure it hasn’t been inside the cart for longer than an hour. If it has, the client cannot remove it. I know, crazy rule, but this is a fake use case, so let’s have some fun, shall we?
  • Update the category counter to make sure the total amount is correct.

Only then, if everything is fine, we can proceed to remove the item from the list.

And here is the relevant code that we’re going to be working with:

1import { useState } from "react"

2

3const fakeItems = [

4 {name: "Ergo keyboard", price: 200, pictureUrl: "https://images.squarespace-cdn.com/content/v1/5a8723cb7131a5121206d464/1625810188933-U8XTJAMWR8A0MJDJ32QA/lain_top.jpg?format=2500w", id: 1, category: 1, timeAdded: new Date() },

5 {name: "Smallice PCB", price: 120, pictureUrl: "https://images.squarespace-cdn.com/content/v1/5a8723cb7131a5121206d464/1617681008502-BJJPF8TRPN6LNTMUBROR/idi7pnppq0d61.jpg?format=2500w", id: 2, category: 2, timeAdded: new Date() },

6 {name: "Smallice Acrylic Case", price: 400, pictureUrl: "https://images.squarespace-cdn.com/content/v1/5a8723cb7131a5121206d464/1623041004115-JPO9UY3R357ZTPUAWM2D/PXL_20210601_203954893.PORTRAIT_2.jpg?format=2500w", id: 3, category: 3, timeAdded: new Date()},

7 {name: "Fourier", price: 320, pictureUrl: "https://images.squarespace-cdn.com/content/v1/5a8723cb7131a5121206d464/1606189295901-T565PVE49OZXN9QK82LD/_RO_5289.jpg?format=2500w", id: 4, category: 4, timeAdded: new Date() }

8];

9

10export default function ShoppingCart() {

11

12 const [items, setItems] = useState(fakeItems)

13

14 async function updateCategory() {

15

16 }

17

18 function checkTime() {

19

20 }

21

22 async function removeItem(id) {

23

24

25

26 let categoryUpdate = await updateCategory()

27 let timeCheck = checkTime()

28

29 if(!categoryUpdate) {

30 return console.log("Error, category was not updated")

31 }

32

33 if(!timeCheck) {

34 return console.log("Error, item has been inside your cart for too long, now you gotta buy it")

35 }

36

37

38 let newItems = items.filter( i => i.id != id)

39 setItems(newItems)

40 }

41

42 return (

43 <ul className="shopping-list">

44 {items.map( i => {

45 return (<li key={i.id} className="shopping-list-item">

46 <img src={i.pictureUrl} />

47 <br /> {i.name} <span className="price">(USD {i.price})</span>

48 &nbsp; <a onClick={ () => removeItem(i.id)})

49 }} href="#">[Remove]</a>

50 </li>)

51 })}

52 </ul>

53 )

54}

The code from above would render something like shown on the following image:

null

Granted, it’s by no means a nice-looking shopping cart, but it’s a list of items that you can remove based on certain conditions. That’ll do. Notice that the logic for category update and time checking is not implemented. That’s not relevant right now, so just assume it works, and we’ll deal with the surrounding logic instead.

With that said, the first thing to note from the code is that onClick handler as it stands right now is calling the removeItem function with the item’s id (the i.id). This is a very common way of passing an attribute to the function, however, one is not going to be enough, we need more attributes, so let’s see what our options are.

# 1. Using an inline arrow function

Following the example from above, a very simple way of passing multiple attributes to your handler, is to call the handler from within an inline arrow function.

1async function removeItem(id, catId, timeAdded) {

2

3

4

5 let categoryUpdate = await updateCategory(catId)

6 let timeCheck = checkTime(timeAdded)

7

8 if(!categoryUpdate) {

9 return console.log("Error, category was not updated")

10 }

11

12 if(!timeCheck) {

13 return console.log("Error, item has been inside your cart for too long, now you gotta buy it")

14 }

15

16

17 let newItems = items.filter( i => i.id != id)

18 setItems(newItems)

19 }

20

21 return (

22 <ul className="shopping-list">

23 {items.map( i => {

24 return (<li key={i.id} className="shopping-list-item">

25 <img src={i.pictureUrl} />

26 <br /> {i.name} <span className="price">(USD {i.price})</span>

27 &nbsp;

28 <a onClick={

29 () => {

30 e.preventDefault();

31 removeItem(i.id, i.category, i.timeAdded)

32 }

33 }} href="#">[Remove]</a>

34 </li>)

35 })}

36 </ul>

37 )

The code added on the onClick line on the <a> element defines an inline arrow function. Since it’s a function definition, it won’t be called immediately, but the closure generated allows you to access the item’s details when it does get called. This method works, don’t get me wrong, however, it litters a bit of the JSX code with business logic for the front-end and if you need to pass a lot of attributes then this line can become a bit complicated to read. On top of that, we’re also having to call the e.preventDefault method to keep the click event from doing anything other than what we actually want. This is just me nitpicking, but if you ask me, I think there are less “crude” ways of doing this. Sometimes hiding a bit of logic can help make the HTML more maintainable. So let’s take a quick look at the alternatives.

# 2. Using the useCallback hook

The useCallback hook will allow us to clean up the code a bit and on top of that, our function will be memoized. For the purposes of this example, the second benefit is not that important, however, it’s definitely an added bonus. The code would look like this:

1export default function ShoppingCart() {

2

3

4

5 const removeHandler = useCallback( (id, catId, timeAdded) => {

6 return async (e) => {

7 e.preventDefault()

8 let categoryUpdate = await updateCategory(catId)

9 let timeCheck = checkTime(timeAdded)

10

11 if(!categoryUpdate) {

12 return console.log("Error, category was not updated")

13 }

14

15 if(!timeCheck) {

16 return console.log("Error, item has been inside your cart for too long, now you gotta buy it")

17 }

18

19 let newItems = items.filter( i => i.id != id)

20 setItems(newItems)

21 }

22

23 }, [items])

24

25 return (

26 <ul className="shopping-list">

27 {items.map( i => {

28 return (<li key={i.id} className="shopping-list-item">

29 <img src={i.pictureUrl} />

30 <br /> {i.name} <span className="price">(USD {i.price})</span>

31 &nbsp;

32 <a onClick={ removeHandler(i.id, i.category, i.timeAdded) } href="#">

33 [Remove]

34 </a>

35 </li>)

36 })}

37 </ul>

38 )

39}

The code with this hook looks like a bit cleaner. For starters, we’re calling a function directly on the onClick handler. There is no longer a need to define an inline arrow function, we can simply call a function and pass the attributes we need. This new function will return the actual handler, which in turn, receives the Event object, so we can safely call the e.preventDefault method from within the handler, instead of right there on the HTML.

This is a better alternative already because we’re abstracting the creation of the handler function and how it should interact with the event. However, we can clean up our code a bit more, by taking advantage of HTML’s data attributes. So let’s take a look.

Open Source Session Replay

Debugging a web application in production may be challenging and time-consuming. OpenReplay is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder. OpenReplay is the only open-source alternative currently available.

OpenReplay

Happy debugging, for modern frontend teams – Start monitoring your web app for free.

# 3. Using data attributes

The data attributes are custom attributes you can add to your HTML and that by default, will be completely ignored by the browser, however you can use them and access them through JavaScript. The point is that you can “embed” the attributes of a cart item into the actual HTML of the element instead of having to pass it directly using code. It’s a neat way to keep the HTML as “clean” as possible. This is how it would look like:

1export default function ShoppingCart() {

2

3

4

5 const removeHandler = async (e) => {

6 e.preventDefault()

7

8 let id = e.target.getAttribute("data-id")

9 let catId = e.target.getAttribute("data-category")

10 let timeAdded = e.target.getAttribute("data-timeadded")

11 let categoryUpdate = await updateCategory(catId)

12 let timeCheck = checkTime(timeAdded)

13

14 if(!categoryUpdate) {

15 return console.log("Error, category was not updated")

16 }

17

18 if(!timeCheck) {

19 return console.log("Error, item has been inside your cart for too long, now you gotta buy it")

20 }

21

22 let newItems = items.filter( i => i.id != id)

23 setItems(newItems)

24 }

25

26 return (

27 <ul className="shopping-list">

28 {items.map( i => {

29 return (<li key={i.id} className="shopping-list-item">

30 <img src={i.pictureUrl} />

31 <br /> {i.name} <span className="price">(USD {i.price})</span>

32 &nbsp;

33 <a

34 data-id={i.id}

35 data-category={i.category}

36 data-timeadded={i.timeAdded}

37 onClick={ removeHandler } href="#">

38 [Remove]

39 </a>

40 </li>)

41 })}

42 </ul>

43 )

44}

Now look at the HTML of the link, you can see the 3 data- attributes containing the values ​​associated with the item. The onClick handler also looks like a lot cleaner, there is no need to pass any attributes into it, you only have to worry about specifying the handler’s name. The only attribute you care about, is the actual event, which React will pass for you. And thr ough the event’s target, you can see how we use the getAttribute method to get the information we need.

The result is a much cleaner HTML, simplified logic and overall a code that not only works, but it’s also a lot easier to read and understand.


In the end, the way you write your code matters only to you and your team. There is not a single way of passing multiple attributes to the onClick handler in React, and all potential options to get the job done, so it’s a matter of understanding what other needs you have and you’re trying to achieve.

If you’ve managed to solve this problem in another way, follow me on Twitter at @ deleteman123 and share your solution.





Source link