Handling Events
Reviewed & published by Brayan K
By the end of this lesson you'll be able to make buttons, inputs, and forms do something — wiring up onClick, onChange, and onSubmit handlers, reading the event object, and stopping the page from reloading.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Attach handlers with onClick, onChange, and onSubmit
- Write event handler functions and pass them by name
- Pass arguments to a handler using an arrow wrapper
- Read the synthetic event object (e.target.value, e.type)
- Use e.preventDefault() to stop a form reloading the page
- Avoid the #1 trap: calling the handler instead of passing it
1. Click Events & Handler Functions
An event is something the user does — a click, a keypress, a form submit. An event handler is a function you write that runs in response. In React you attach one with a camelCase prop like onClick (not the lowercase onclick from HTML), and you pass the function by name. The golden rule: onClick={handleClick} — no parentheses. The parentheses would call the function immediately; you want to hand the function to React so it can call it on each click.
Read the worked example below. The handleLike function is exactly what you'd pass to onClick — here we call it three times to simulate three clicks and watch the count climb.
// React calls your handler. Here we run the SAME handler logic in plain JS
// so you can see exactly what happens each time a "click" fires.
let likeCount = 0;
// This is the kind of function you'd pass to onClick={...}
function handleLike() {
likeCount = likeCount + 1;
console.log("Liked! Total likes: " + likeCount);
}
// Simulate three clicks. In React, the runtime calls handleLike for you.
handleLike(); // Liked! Total likes: 1
handleLike(); // Liked! Total likes: 2
handleLike(); // Liked! Total likes: 3
console.log("Final count:", likeCount); // Final count: 3The most important detail: pass it, don't call it
This single distinction trips up almost every beginner. Burn it in now:
Now you try. Finish the handler and "click" the button by calling it.
// 🎯 YOUR TURN — fill in the blanks marked ___ then press "Try it Yourself".
let count = 0;
// 1) Finish this handler so it adds 1 to count and logs the new total.
function handleIncrement() {
count = ___; // 👉 count + 1
console.log("Count is now: " + count);
}
// In React you would write: <button onClick={handleIncrement}>+1</button>
// Notice: NO parentheses — you pass the function, you don't call it.
// 2) "Click" the button three times by calling the handler 3 times.
handleIncrement();
___ // 👉 handleIncrement();
handleIncrement();
// ✅ Expected output:
// Count is now: 1
// Count is now: 2
// Count is now: 32. Passing Arguments to Handlers
What if your handler needs data — like which item to delete? You can't write onClick={deletePost(id)} because, as you just learned, that calls deletePost immediately during render. The fix is an arrow wrapper: onClick={() => deletePost(id)}. The arrow function is the handler; it sits and waits, and only calls deletePost(id) when the click actually happens.
// Passing arguments: wrap the call in an arrow function () => fn(arg).
// The arrow function is the handler; it calls deletePost WHEN clicked.
function deletePost(id) {
console.log("Deleting post #" + id);
}
const posts = [101, 102, 103];
// In JSX you'd write: onClick={() => deletePost(id)}
// Here we build those arrow handlers and call them to prove they work.
posts.forEach((id) => {
const handler = () => deletePost(id); // the wrapper React would store
handler(); // React would call this on click
});
// Deleting post #101
// Deleting post #102
// Deleting post #103
// ⚠️ onClick={deletePost(id)} would call deletePost DURING render — bad.
// The arrow wrapper delays the call until the click actually happens.3. The Event Object & onChange
When React calls your handler, it passes one argument: the event object. React calls it a SyntheticEvent — a cross-browser wrapper around the native DOM event, so the same properties work in every browser. The one you'll use constantly is e.target.value: the current text of an input. The onChange event fires on every keystroke, which is how you keep React state in sync with what the user types (a controlled component).
// The event object (React calls it a SyntheticEvent) is passed to your
// handler automatically. It's a cross-browser wrapper around the real
// DOM event, with the same properties everywhere.
// Pretend React handed us this event from typing "Hi" in a text box:
const fakeEvent = {
type: "change",
target: { value: "Hi", name: "username" },
preventDefault() { console.log("(default behaviour prevented)"); },
};
// In an onChange handler you read the typed text from e.target.value:
function handleChange(e) {
console.log("Event type: " + e.type); // change
console.log("Input name: " + e.target.name); // username
console.log("Typed value: " + e.target.value); // Hi
}
handleChange(fakeEvent);
// Event type: change
// Input name: username
// Typed value: HiIn real JSX, that handler wires an input to state like this:
4. Forms & preventDefault()
Forms have a built-in browser behaviour: submitting one reloads the page. In a React app that's a disaster — a reload throws away all your state and re-fetches everything. So in your onSubmit handler you call e.preventDefault() as the very first line. That cancels the default reload and lets you handle the submit in JavaScript instead. Put it on <form onSubmit={...}>, not the button — that way pressing Enter inside the form works too.
// onSubmit fires when a form is submitted (button click OR Enter key).
// By default the browser RELOADS the page — which wipes your React state.
// e.preventDefault() stops that so YOU handle the submit in JavaScript.
function handleSubmit(e) {
e.preventDefault(); // without this, the page reloads
console.log("Form handled in JS — no reload!");
console.log("Query:", e.target.query);
}
// Simulate the event React would pass to onSubmit:
const submitEvent = {
target: { query: "react events" },
preventDefault() { console.log("→ default reload cancelled"); },
};
handleSubmit(submitEvent);
// → default reload cancelled
// Form handled in JS — no reload!
// Query: react eventsYour turn — complete the submit handler so the page is never reloaded and the typed email is logged.
// 🎯 YOUR TURN — complete the form submit handler.
const event = {
target: { value: "[email protected]" },
preventDefault() { console.log("(page reload stopped)"); },
};
function handleSubmit(e) {
// 1) Stop the browser from reloading the page.
___; // 👉 e.preventDefault()
// 2) Read what the user typed and log it.
const email = ___; // 👉 e.target.value
console.log("Signing up: " + email);
}
handleSubmit(event);
// ✅ Expected output:
// (page reload stopped)
// Signing up: [email protected]Common Errors (and the fix)
- Calling the handler immediately: onClick={handleClick()} runs on render and fires once (or loops forever if it sets state). Pass it by name: onClick={handleClick}, or wrap it: onClick={() => handleClick(id)}.
- "Maximum update depth exceeded": the classic symptom of the bug above — onClick={setCount(count + 1)} calls setCount during render, which re-renders, which calls it again. Use an arrow wrapper: onClick={() => setCount(count + 1)}.
- The page reloads / state vanishes on submit: you forgot e.preventDefault(). Make it the first line of your onSubmit handler.
- "Cannot read properties of undefined (reading 'value')": your handler reads e.target.value but you forgot to accept the event: write function handleChange(e) {… with the e parameter.
- "this is undefined" in an old class component: a method passed as onClick={this.handleClick} loses its this. Bind it in the constructor (this.handleClick = this.handleClick.bind(this)) or use a class field arrow. Function components with hooks avoid this entirely — another reason they're now standard.
Pro Tips
- 💡 Name handlers handleX: handleSubmit, handleDelete, handleNameChange — the convention makes code instantly readable.
- 💡 Only add an arrow wrapper when you need an argument. onClick={handleClick} is cleaner than onClick={() => handleClick()} when there's no argument.
- 💡 preventDefault goes first. Call it at the top of the handler so an early return can never skip it.
- 💡 Put onSubmit on the <form>, not the button — then Enter-to-submit works for free.
📋 Quick Reference
| Task | Code |
|---|---|
| Click handler | <button onClick={handleClick}> |
| Pass an argument | onClick={() => del(id)} |
| Read input text | onChange={(e) => set(e.target.value)} |
| Form submit | <form onSubmit={handleSubmit}> |
| Stop page reload | e.preventDefault() |
| Key pressed | e.key === 'Enter' |
| Stop bubbling | e.stopPropagation() |
Frequently Asked Questions
Q: Why is it onClick and not onclick?
JSX uses camelCase for event props, so it's onClick, onChange, onSubmit. It's also a different mechanism from HTML — you pass a real JavaScript function, not a string of code.
Q: When do I need the arrow function () => wrapper?
Only when you need to pass an argument, e.g. onClick={() => deletePost(id)}. If the handler takes no arguments, pass it by name: onClick={handleClick}.
Q: Where does the e in handleChange(e) come from?
React passes it automatically. Whenever your handler is the one React calls directly, the first argument is the SyntheticEvent — just add an e parameter to receive it.
Q: What happens if I forget e.preventDefault() in a form?
The browser does its default thing and reloads the page, which destroys your React state and re-runs the whole app. Always call it first in an onSubmit handler.
Mini-Challenge: A Toggle Handler
No blanks this time — just a brief and an outline. Write the handler yourself, "click" it three times, and match the expected output. This is the exact pattern behind every show/hide, dark-mode, and accordion button you'll ever build.
// 🎯 MINI-CHALLENGE: A toggle handler
//
// Imagine a "Show / Hide" button. You'll write the handler logic.
//
// 1. Create a variable "isOpen" set to false.
// 2. Write a function toggle() that flips isOpen (true -> false -> true)
// and logs: "Panel is now: open" or "Panel is now: closed".
// Hint: isOpen = !isOpen; flips a boolean.
// 3. In React you'd attach it with: <button onClick={toggle}>Toggle</button>
// Here, "click" the button by calling toggle() three times.
//
// ✅ Expected output:
// Panel is now: open
// Panel is now: closed
// Panel is now: open
// your code here🎉 Lesson Complete
- ✅ Attach handlers with camelCase props: onClick, onChange, onSubmit
- ✅ Pass a function by name — onClick={fn}, never onClick={fn()}
- ✅ Use an arrow wrapper () => fn(arg) to pass arguments
- ✅ React passes a SyntheticEvent e; read input text from e.target.value
- ✅ Call e.preventDefault() first in onSubmit to stop the page reloading
Practice quiz
How do you correctly attach a click handler in JSX?
- onClick={handleClick()}
- onclick={handleClick}
- onClick={handleClick}
- onClick="handleClick"
Answer: onClick={handleClick}. Pass the function by name without parentheses: onClick={handleClick}. React calls it on each click.
What does onClick={handleClick()} do?
- Calls handleClick immediately during render and uses its return value as the handler
- Calls handleClick on each click
- Nothing
- Throws a syntax error
Answer: Calls handleClick immediately during render and uses its return value as the handler. The parentheses call it right away at render time; the return value (often undefined) becomes the handler.
Why does JSX use onClick rather than the HTML onclick?
- They are the same thing
- onclick is deprecated in browsers
- onClick is faster
- JSX uses camelCase event props and passes a function, not a string
Answer: JSX uses camelCase event props and passes a function, not a string. JSX event props are camelCase (onClick, onChange) and take a real JavaScript function, not a code string.
How do you pass an argument to a handler on click?
- onClick={deletePost(id)}
- onClick={() => deletePost(id)}
- onClick={deletePost, id}
- onClick={deletePost.bind(id)}
Answer: onClick={() => deletePost(id)}. Wrap it in an arrow function: onClick={() => deletePost(id)} so the call is delayed until the click.
What does React call the event object passed to your handler?
- A SyntheticEvent
- A NativeEvent
- A DOMEvent
- A ReactEvent
Answer: A SyntheticEvent. React passes a SyntheticEvent — a cross-browser wrapper around the native DOM event.
In an onChange handler, how do you read the current text of an input?
- e.value
- e.input.text
- e.target.value
- e.currentValue
Answer: e.target.value. e.target.value holds the input's current text; onChange fires on every keystroke.
Why call e.preventDefault() in a form's onSubmit handler?
- To submit faster
- To stop the browser from reloading the page (which wipes React state)
- To clear the form
- To focus the first input
Answer: To stop the browser from reloading the page (which wipes React state). By default a form submit reloads the page, destroying React state; preventDefault stops that.
Where should you put the onSubmit handler so Enter-to-submit works?
- On the submit button
- On each input
- On the document
- On the <form> element
Answer: On the <form> element. Put onSubmit on the <form>; then both button clicks and pressing Enter trigger it.
You get 'Cannot read properties of undefined (reading value)' in handleChange. Likely cause?
- The input has no value
- You forgot to accept the event parameter e
- You used onClick instead of onChange
- preventDefault was missing
Answer: You forgot to accept the event parameter e. The handler must accept the event: function handleChange(e) — then read e.target.value.
What commonly causes 'Maximum update depth exceeded' with an onClick?
- Using an arrow wrapper
- Reading e.target.value
- Calling setState during render, e.g. onClick={setCount(count + 1)}
- Putting onSubmit on the form
Answer: Calling setState during render, e.g. onClick={setCount(count + 1)}. onClick={setCount(...)} runs setState during render, re-rendering and re-calling it. Use onClick={() => setCount(...)}.
Continue this course
- Previous: Fragments & Multiple Elements
- Next: Conditional Rendering — Show and hide UI with &&, ternaries, and early returns
- Quick reference: React cheat sheet › State & Events