React wraps native DOM events in a cross-browser wrapper (historically called SyntheticEvent), attached via camelCase props:
function Button() {
function handleClick(e) {
e.preventDefault();
console.log("clicked");
}
return <button onClick={handleClick}>Click me</button>;
}
Passing arguments to a handler needs a wrapping arrow function, since JSX event props expect a function reference, not a function call:
// Wrong — calls deleteItem(id) immediately during render, not on click
<button onClick={deleteItem(id)}>Delete</button>
// Right — passes a new function that calls deleteItem(id) when clicked
<button onClick={() => deleteItem(id)}>Delete</button>
Under the hood, React historically attaches most event listeners once at the root of the app and uses event delegation/bubbling to figure out which component's handler should fire, rather than attaching a real native listener to every single DOM node — an implementation detail that mostly doesn't matter to app code, except that it's why calling e.stopPropagation() inside a React handler stops other React handlers from firing predictably, following the same bubble order as the JSX tree.