Handling events
React handles events through props named like onClick and onChange, written in camelCase, and you give each one a function to call.
import { useState } from "react";
function App() {
const [message, setMessage] = useState("Nothing clicked yet");
function handleClick() {
setMessage("Thanks for clicking!");
console.log("clicked at", new Date().toLocaleTimeString());
}
return (
<div>
<button onClick={handleClick}>Click me</button>
<p>{message}</p>
</div>
);
}onClick={handleClick} hands React the function itself, to call later. A common mistake is onClick={handleClick()}, with brackets: that calls the function immediately, during rendering, instead of on click.
Passing an argument
import { useState } from "react";
function App() {
const [choice, setChoice] = useState(null);
const sizes = ["Small", "Medium", "Large"];
return (
<div>
{sizes.map((size) => (
<button key={size} onClick={() => setChoice(size)} style={{ marginRight: 6 }}>
{size}
</button>
))}
<p>{choice ? `You picked ${choice}` : "Pick a size"}</p>
</div>
);
}To pass an argument, wrap the call in an arrow function: onClick={() => setChoice(size)}. The arrow function is what React calls on a click, and it then calls setChoice with the right size.
The event object
import { useState } from "react";
function App() {
const [text, setText] = useState("");
const [lastKey, setLastKey] = useState("");
return (
<div>
<input
value={text}
placeholder="Type something"
onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => setLastKey(e.key === " " ? "Space" : e.key)}
/>
<p>You typed: {text}</p>
<p>Last key: {lastKey}</p>
<p>{text.length} characters</p>
</div>
);
}React passes every handler an event object, usually called e. e.target is the element where the event happened, so e.target.value is an input's current text, and e.key names the key that was pressed.
Forms: preventDefault
import { useState } from "react";
function App() {
const [email, setEmail] = useState("");
const [status, setStatus] = useState("");
function handleSubmit(e) {
e.preventDefault(); // stop the browser from reloading the page
setStatus(`Subscribed ${email}`);
setEmail("");
}
return (
<form onSubmit={handleSubmit}>
<input type="email" required value={email} onChange={(e) => setEmail(e.target.value)} placeholder="you@example.com" />{" "}
<button>Subscribe</button>
<p>{status}</p>
</form>
);
}Handle onSubmit on the form rather than onClick on the button, so pressing Enter works too. e.preventDefault() cancels the browser's default action, which for a form is sending it and reloading the page.
Mouse events
import { useState } from "react";
function App() {
const [pos, setPos] = useState({ x: 0, y: 0 });
const [inside, setInside] = useState(false);
return (
<div
onMouseMove={(e) => {
const box = e.currentTarget.getBoundingClientRect();
setPos({ x: Math.round(e.clientX - box.left), y: Math.round(e.clientY - box.top) });
}}
onMouseEnter={() => setInside(true)}
onMouseLeave={() => setInside(false)}
style={{ height: 150, borderRadius: 12, display: "grid", placeItems: "center",
background: inside ? "#efedff" : "#f3f4f8" }}
>
{inside ? `x: ${pos.x}, y: ${pos.y}` : "Move your mouse in here"}
</div>
);
}e.currentTarget is the element the handler is attached to, while e.target may be a child inside it.
Stopping an event from bubbling up
import { useState } from "react";
function App() {
const [log, setLog] = useState([]);
const add = (msg) => setLog((old) => [...old, msg].slice(-4));
return (
<div>
<div onClick={() => add("card clicked")} style={{ padding: 16, border: "1px solid #ccd", borderRadius: 10 }}>
<strong>A card</strong>
<p>Click anywhere on the card, then click Delete.</p>
<button onClick={(e) => { e.stopPropagation(); add("delete clicked, and only delete"); }}>Delete</button>
</div>
<ul>{log.map((msg, i) => <li key={i}>{msg}</li>)}</ul>
</div>
);
}Events bubble: a click on the button is also a click on the card around it, so both handlers would run. e.stopPropagation() stops the event before it reaches the card.