Skip to content
React rc state-events 4 min read

Handling Events

Interactivity in React comes down to responding to events—a click, a keystroke, a form submission. You wire these up by passing functions to special JSX props such as onClick and onChange. React handles the low-level DOM plumbing for you and gives every handler a consistent, cross-browser event object, so the same code behaves the same everywhere.

Event props are camelCase

In plain HTML you write attributes like onclick in lowercase. In JSX, event props are camelCased: onClick, onChange, onSubmit, onMouseEnter, onKeyDown, and so on. The value you assign is a JavaScript expression inside curly braces, not a string.

function SaveButton() {
  return <button onClick={() => console.log("saved")}>Save</button>;
}

The most common event props map directly to familiar DOM events:

JSX propFires when…Common element
onClickthe element is clicked<button>
onChangean input’s value changes<input>
onSubmita form is submitted<form>
onKeyDowna key is pressedany focusable
onMouseEnterthe pointer enters the elementany element

Pass the function, don’t call it

This is the single most common beginner mistake. You must pass a function reference, not the result of calling it.

function Counter() {
  function handleClick() {
    console.log("clicked");
  }

  // ✅ correct — pass the function; React calls it on click
  return <button onClick={handleClick}>Click me</button>;
}

If you write onClick={handleClick()} with parentheses, JavaScript calls handleClick immediately during render and assigns its return value (usually undefined) to onClick. The handler then runs once at render time instead of on each click.

Rule of thumb: onClick={handleClick} (no parens) for a named handler, or onClick={() => handleClick()} (an arrow wrapper) when you need to control how it’s called.

The synthetic event object

When an event fires, React passes your handler a SyntheticEvent—a thin, cross-browser wrapper around the native DOM event. It exposes the same API you already know: event.target, event.preventDefault(), event.stopPropagation(), event.key, and more. Because the interface is normalized, you don’t have to worry about browser quirks.

function NameField() {
  function handleChange(event) {
    // event.target is the <input>; .value is its current text
    console.log(event.target.value);
  }

  return <input onChange={handleChange} placeholder="Type your name" />;
}

By convention the parameter is named event or just e. Access the native event underneath with event.nativeEvent if you ever need it.

Passing arguments to a handler

Often a handler needs extra data—which item was clicked, for instance. Wrap the call in an inline arrow function so the argument is supplied only when the event fires:

function ItemList({ items, onDelete }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.label}
          <button onClick={() => onDelete(item.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

The arrow function () => onDelete(item.id) is the reference passed to onClick; onDelete runs with the right id only on click. If you need the event too, include it: onClick={(e) => onDelete(item.id, e)}.

preventDefault and stopPropagation

Some elements have built-in browser behavior. A <form> reloads the page on submit; a link navigates. Call event.preventDefault() to suppress that default behavior and handle things yourself.

import { useState } from "react";

function LoginForm() {
  const [email, setEmail] = useState("");

  function handleSubmit(event) {
    event.preventDefault();        // stop the full-page reload
    console.log("Submitting", email);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <button type="submit">Log in</button>
    </form>
  );
}

Output:

Submitting [email protected]

event.stopPropagation() is different: it stops the event from bubbling up to parent handlers. Use it when an inner click shouldn’t trigger an outer one.

function Card({ onOpen }) {
  return (
    <div onClick={onOpen}>
      <h3>Project</h3>
      <button
        onClick={(e) => {
          e.stopPropagation();     // don't trigger the card's onOpen
          console.log("liked");
        }}
      >
        Like
      </button>
    </div>
  );
}

Inline vs named handlers

Both styles are valid; choose by readability.

StyleLooks likeBest for
InlineonClick={() => setOpen(true)}one-liners, passing arguments
NamedonClick={handleOpen}multi-step logic, reuse, easier testing

Inline handlers keep trivial logic close to the markup. Once a handler grows beyond a line or two—or you reuse it—extract a named function (conventionally prefixed handle) for clarity.

Best Practices

  • Pass a function reference (onClick={handleClick}), never a call (onClick={handleClick()}).
  • Name handlers with a handle prefix and name the props that accept them with an on prefix.
  • Use an inline arrow wrapper to pass arguments: onClick={() => doThing(id)}.
  • Call event.preventDefault() on form submits to stop the page reload.
  • Reach for event.stopPropagation() only when you genuinely need to stop bubbling—overusing it makes behavior hard to trace.
  • Extract inline handlers into named functions once the logic exceeds a line or two.
Last updated June 14, 2026
Was this helpful?