React Passing Arguments to Event Handlers
Table of Contents + −
In the last lesson on React Click Events you learned how to run a function when a button is clicked. Now let’s learn how to tell that function which item was clicked, like sending the right id to a delete handler.
🤔 Why do we need this?
Here’s the pain. Most real handlers need to know what they’re acting on.
- You have a list of items. Each item has its own delete button.
- When a button is clicked, your
handleDeletefunction needs theidof that item. - But
onClickonly gives you the event by default. It doesn’t know about yourid. - So you have to find a way to send your own data into the handler.
The thing is, the obvious way to do this runs your function at the wrong time. Let’s see why, then fix it with a small arrow function wrapper.
💥 The classic bug: calling on render
Beginners almost always try this first. It looks correct, but it breaks.
This code tries to pass id directly to the handler.
function DeleteButton({ id }) { function handleDelete(itemId) { console.log("Deleting", itemId); }
// ❌ Wrong - this CALLS handleDelete right now, during render return <button onClick={handleDelete(id)}>Delete</button>;}Read what onClick={handleDelete(id)} actually means.
- React reads your JSX to draw the screen. That’s called render.
- The
( )afterhandleDeletemeans “call this function now”. - So React runs
handleDelete(id)while drawing, before anyone clicks anything. onClickthen gets whatever the function returned, not the function itself.
So that’s the bug. Here’s what goes wrong:
- Your handler fires at the wrong moment, during render instead of on the click.
- And if
handleDeleteupdates state, it can run again and again in a loop.
Parentheses mean 'call it now'
Writing onClick={handleDelete(id)} calls the function during render. onClick should receive a function to run later, on the click, not the result of calling it right away.
✅ The fix: wrap it in an arrow function
The fix is small. You wrap your call inside an arrow function. Now onClick gets a function to run later, not the result of a call.
This is the wrong way and the right way, side by side.
function DeleteButton({ id }) { function handleDelete(itemId) { console.log("Deleting", itemId); }
// ❌ Wrong - runs immediately during render // return <button onClick={handleDelete(id)}>Delete</button>;
// ✅ Right - runs only when the button is clicked return <button onClick={() => handleDelete(id)}>Delete</button>;}Here’s why the arrow function fixes everything.
() => handleDelete(id)is a brand-new function. It does not run yet.- You hand that function to
onClick. React just keeps it for later. - When someone clicks, React runs the arrow function.
- The arrow function then calls
handleDelete(id)with youridinside.
So the call is now delayed until the click. That’s the whole trick. The arrow function is a little wrapper that holds your argument until the right moment.
📋 A real list example
This pattern shows up the most with lists. Each row needs to pass its own id.
Here’s a list of users, where each delete button sends the matching id.
function UserList() { const users = [ { id: 1, name: "Alex" }, { id: 2, name: "Riya" }, { id: 3, name: "Arjun" }, ];
function handleDelete(id) { console.log("Delete user with id:", id); }
return ( <ul> {users.map((user) => ( <li key={user.id}> {user.name} <button onClick={() => handleDelete(user.id)}>Delete</button> </li> ))} </ul> );}Let’s walk through what each button does.
users.map(...)draws one<li>for every user in the list.- Each button gets its own arrow function:
() => handleDelete(user.id). - Alex’s button remembers
id: 1, Riya’s remembersid: 2, and so on. - Click Riya’s delete button and only
2gets passed in.
So each row carries its own id without any extra work. This is the everyday pattern you’ll use again and again.
Output
Delete user with id: 2🎯 Passing the event AND your argument
Sometimes you need both things at once.
- You want your own
id, and the event object React normally gives you. - A common case is calling
e.preventDefault()while still knowing which item was clicked.
This handler takes the event first, then your id.
function handleClick(e, id) { e.preventDefault(); console.log("Clicked item:", id);}
// ✅ Pass the event in, then add your own argument<button onClick={(e) => handleClick(e, id)}>Select</button>;Here’s how the two values get in.
- The arrow function receives the event object as
e. React hands it over on the click. - You then call your handler and pass both:
handleClick(e, id). - Inside the handler,
eis the event andidis your own data. - Order is up to you, but event-first then your argument is the common style.
So you’re not choosing between the event and your data. The arrow function lets you forward both.
No argument? Skip the wrapper
If a handler needs nothing from you, just pass it by name: onClick={handleClick}. React gives it the event automatically. You only need the arrow wrapper when you’re sending your own extra argument.
🧩 What You’ve Learned
- ✅
onClick={handleDelete(id)}is a bug. The( )calls the function during render, not on the click - ✅ Wrap the call in an arrow function so it runs later:
onClick={() => handleDelete(id)} - ✅ The arrow function is a small wrapper that holds your argument until the button is actually clicked
- ✅ This is the standard pattern for lists, where each item passes its own
id - ✅ To get both values, take the event first:
onClick={(e) => handleClick(e, id)} - ✅ If a handler needs no extra data, just pass it by name:
onClick={handleClick}
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why is onClick={handleDelete(id)} a bug?
Why: The parentheses call the function immediately while React is rendering. onClick then receives the return value, not a function to run on the click.
- 2
What is the correct way to pass an id to a click handler?
Why: Wrapping the call in an arrow function delays it. React keeps the arrow function and only runs it, with your id, when the button is clicked.
- 3
How do you pass both the event object and your own argument?
Why: The arrow function receives the event as e, then you forward both e and id into your handler: handleClick(e, id).
- 4
When can you pass a handler by name, like onClick={handleClick}?
Why: If the handler does not need any extra data from you, passing it by name works and React supplies the event for free. The arrow wrapper is only for sending your own argument.
🚀 What’s Next?
Now you can send any data you want into a handler without the call-on-render bug. Next you’ll handle a very common case where the event object really matters: working with forms and reading what the user typed.