React Basics #6: Event Handling
Last time you learned about state and useState, and along the way you naturally used the onClick event handler. It worked, but there are a few things worth knowing about how React handles events. This time we’ll dig into event handling.
How to handle events in React #
In React, you handle an event by passing a handler function as a JSX attribute. The only difference from HTML’s onclick is that you use camelCase onClick.
function App() {
function handleClick() {
alert('The button was clicked!');
}
return <button onClick={handleClick}>Click</button>;
}
export default App;There’s one common mistake here. Don’t call the function (handleClick()) — pass the function itself (handleClick).
<button onClick={handleClick()}>Click</button>If you write it this way, handleClick() runs the moment the component renders, and the alert pops up. On top of that, what gets registered as onClick is handleClick’s return value (undefined), so an actual click does nothing.
<button onClick={handleClick}>Click</button>You pass only the function reference, and React calls it at click time. Remember this distinction.
Inline handlers #
For simple handlers, you can write an arrow function directly inside JSX.
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}() => setCount(count + 1) is an anonymous function called at click time. For one-liner handlers, inlining like this is convenient; for longer logic, it’s more readable to extract a separate function. There’s no fixed rule — it depends on your team’s or your own preference.
Passing arguments to a function #
When you need to pass an argument to a handler, wrap it in an inline arrow function.
function App() {
function handleClick(name) {
alert(`Hello, ${name}!`);
}
return (
<>
<button onClick={() => handleClick('Alice')}>Greet Alice</button>
<button onClick={() => handleClick('Bob')}>Greet Bob</button>
</>
);
}Don’t write it like this:
<button onClick={handleClick('Alice')}>...</button>As you saw above, this gets called immediately on render. To pass an argument, you must wrap it in an arrow function so it means “call this when clicked.”
The event object #
An event handler receives an event object as its first parameter. This object holds information about which element fired which event.
import { useState } from 'react';
function InputDemo() {
const [text, setText] = useState('');
function handleChange(e) {
setText(e.target.value);
}
return (
<div>
<input type="text" value={text} onChange={handleChange} />
<p>Input value: {text}</p>
</div>
);
}
export default InputDemo;The parameter is usually named e or event. e.target is the DOM element that fired the event, and e.target.value gives you the input’s value.
e.preventDefault(), e.target, and e.key as is.Common events #
The most frequently used event handlers:
onClick— clickingonChange— when the value of an input element (input, textarea, select) changesonSubmit— when a form is submittedonKeyDown/onKeyUp— when a keyboard key is pressed or releasedonMouseEnter/onMouseLeave— when the mouse enters or leaves an elementonFocus/onBlur— focus enters/leaves
Each event puts the relevant information on the event object. For onChange, look at e.target.value; for onKeyDown, look at e.key (the name of the pressed key).
function SearchBox() {
function handleKeyDown(e) {
if (e.key === 'Enter') {
alert('Enter key was pressed');
}
}
return <input type="text" onKeyDown={handleKeyDown} />;
}Preventing default behavior #
Browsers have default behaviors for some events. Submitting a form refreshes the page; clicking a link navigates. To prevent the default behavior, call preventDefault() on the event object.
import { useState } from 'react';
function LoginForm() {
const [email, setEmail] = useState('');
function handleSubmit(e) {
e.preventDefault(); // prevent the page refresh caused by form submission
console.log('submitted email:', email);
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Log in</button>
</form>
);
}
export default LoginForm;A form fires onSubmit automatically when you click the submit button or press Enter inside an input. Without e.preventDefault(), the browser would refresh the page, defeating any handling logic you wrote.
Passing event handlers as props #
An event handler is just a function, so you can pass it as a prop to a child component. This pattern is very common when a parent needs to handle an event raised by its child.
function Button({ label, onClick }) {
return (
<button onClick={onClick} style={{ padding: '8px 16px' }}>
{label}
</button>
);
}
export default Button;import Button from './Button';
function App() {
function handleSave() {
alert('Saved');
}
function handleCancel() {
alert('Cancelled');
}
return (
<>
<Button label="Save" onClick={handleSave} />
<Button label="Cancel" onClick={handleCancel} />
</>
);
}The parent passes down what to do (the handler), and the child reports when that thing happens (a click). By convention, handler prop names start with on (onClick, onSave, onItemSelect, …).
Updating state inside a handler #
You saw this in the previous article — updating state inside an event handler is the most common pattern.
import { useState } from 'react';
function Toggle() {
const [isOn, setIsOn] = useState(false);
function handleToggle() {
setIsOn(prev => !prev);
}
return (
<div>
<p>Current: {isOn ? 'ON' : 'OFF'}</p>
<button onClick={handleToggle}>Toggle</button>
</div>
);
}
export default Toggle;An event happens → the handler runs → state updates → the screen re-renders. This flow is the most fundamental pattern in a React app.
Try it yourself #
Let’s build a simple input form. The user enters a name and a message, and pressing “Add” displays the message below (we’ll extend this in #7 and #8).
Create src/MessageForm.jsx.
import { useState } from 'react';
function MessageForm() {
const [name, setName] = useState('');
const [message, setMessage] = useState('');
const [lastSubmitted, setLastSubmitted] = useState(null);
function handleSubmit(e) {
e.preventDefault();
if (!name || !message) return;
setLastSubmitted({ name, message });
setName('');
setMessage('');
}
return (
<div style={{ padding: '16px', border: '1px solid #ccc', borderRadius: '8px' }}>
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
<div style={{ marginTop: '8px' }}>
<input
type="text"
placeholder="Message"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
</div>
<button type="submit" style={{ marginTop: '8px' }}>Add</button>
</form>
{lastSubmitted && (
<p style={{ marginTop: '12px' }}>
Last submitted: <strong>{lastSubmitted.name}</strong> — {lastSubmitted.message}
</p>
)}
</div>
);
}
export default MessageForm;Wire it up in src/App.jsx.
import MessageForm from './MessageForm';
function App() {
return (
<>
<h1>Message form</h1>
<MessageForm />
</>
);
}
export default App;Type a name and a message and press Enter or the “Add” button. The most recently submitted value appears below, and the input fields are cleared. Try removing e.preventDefault() once and see how the form refreshes the page and your input vanishes.
lastSubmitted && (...) part is something you haven’t seen before — it’s conditional rendering. The pattern is: show this when the value is truthy, hide it otherwise. We’ll cover it in detail in #7.Wrapping up #
In this article we looked at React’s event handling. The essentials:
- Register handlers via camelCase attributes like
onClick - Pass the function — don’t call it (
{handleClick}, not{handleClick()}) - Wrap in an arrow function to pass arguments
- Handlers receive the event object (
e) as their first parameter - Use
e.preventDefault()to block the browser’s default behavior - Handlers can be passed down to children as props (names that start with
on)
Also, the {lastSubmitted && ...} pattern you saw briefly in the MessageForm example leads directly into the next topic. In the next article, “React Basics #7: Conditional Rendering,” we’ll round up the various patterns for showing, hiding, or swapping parts of the screen based on state.