Events and Inputs in React

Event handlers let components respond to people. Input elements can either take their current value from React state or keep that value in the DOM.

Event handlers respond to interaction

An event handler is a function that React runs after an interaction such as a click, key press, form submission, or input change. Handler prop names use camel case, including onClick, onChange, and onSubmit.

Pass the function itself to the event prop. React calls it when the event happens.
function DownloadButton() {
  function handleClick() {
    alert("Download started");
  }

  return (
    <button onClick={handleClick}>
      Download
    </button>
  );
}

Pass, do not call. onClick={handleClick} waits for a click. onClick={handleClick()} calls the function during rendering and assigns its return value as the handler.

An inline arrow function is useful when a handler needs an argument.
function ProductButton({ productId }) {
  function addToCart(id) {
    console.log(`Adding ${id}`);
  }

  return (
    <button onClick={() => addToCart(productId)}>
      Add to cart
    </button>
  );
}

React passes an event object to the handler. Its target is the element where the event began, while currentTarget is the element whose handler is currently running.

Prevent the browser's page navigation while React handles the form submission.
function SearchForm() {
  function handleSubmit(event) {
    event.preventDefault();
    console.log("Searching");
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="query" />
      <button type="submit">Search</button>
    </form>
  );
}

Event propagation moves through the tree

Most React events bubble. After a child handler runs, the event continues upward and can run handlers on its ancestors. This makes a parent useful for handling interactions from many descendants.

Clicking the button logs both messages because the click bubbles to the toolbar.
function Toolbar() {
  return (
    <div onClick={() => console.log("Toolbar clicked")}>
      <button onClick={() => console.log("Save clicked")}>
        Save
      </button>
    </div>
  );
}
Stop propagation when this click should belong only to the button.
<button
  onClick={(event) => {
    event.stopPropagation();
    console.log("Only the button handles this click");
  }}
>
  Save
</button>
  • stopPropagation() controls React propagation.

    It stops the event from reaching handlers higher in the React tree.

  • preventDefault() controls browser behavior.

    It stops a default action such as submitting a form or following a link. It does not stop bubbling.

  • Capture handlers run first.

    Use a name such as onClickCapture on an ancestor to observe the event while it travels down toward the target, even if a child later stops bubbling.

Controlled inputs read from React state

A controlled input receives its current value or checked prop from React state. Its change handler updates that state. React state is therefore the source of truth for what appears in the input.

Each edit triggers onChange, updates state, and renders the new value.
import { useState } from "react";

function NameField() {
  const [name, setName] = useState("");

  return (
    <label>
      Name
      <input
        value={name}
        onChange={(event) => setName(event.target.value)}
      />
      <span> {name.length} characters</span>
    </label>
  );
}

Controlled inputs are a good fit when the interface must validate, format, calculate from, synchronize, or reset a value while the person edits it.

A controlled input needs an onChange handler that updates its backing value. Without one, a fixed value makes the input effectively read-only. Initialize text state with "" so the input stays controlled for its whole lifetime.

Uncontrolled inputs keep their value in the DOM

An uncontrolled input manages its current value itself. React may provide an initial value with defaultValue or defaultChecked, but it does not send a new value on every render. Read the current data when it is needed.

FormData reads named uncontrolled fields when the form is submitted.
function ContactForm() {
  function handleSubmit(event) {
    event.preventDefault();

    const data = new FormData(event.currentTarget);
    console.log(data.get("email"));
    event.currentTarget.reset();
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Email
        <input
          name="email"
          type="email"
          defaultValue="reader@example.com"
        />
      </label>
      <button type="submit">Send</button>
    </form>
  );
}

Choose controlled

Use state when other UI needs the value during editing, or React must transform and validate it immediately.

Choose uncontrolled

Let the DOM keep simple form values when the application only needs them at submission time.

Do not switch one input between controlled and uncontrolled. Choose value with onChange, or choose defaultValue and let the DOM manage later edits. File inputs are always uncontrolled because scripts cannot set their value.

Check your understanding

  1. Why is onClick={() => save(id)} different from onClick={save(id)}?
    Show answer

    The arrow function waits for the click before calling save. The second expression calls save immediately during rendering and gives React its return value.

  2. Does preventDefault() stop a click from reaching a parent handler?
    Show answer

    No. It stops the browser's default action. Use stopPropagation() when the event should not bubble to React handlers above it.

  3. Which prop provides only the initial text for an uncontrolled input?
    Show answer

    Use defaultValue. The DOM owns subsequent edits, while a controlled input instead receives its current value from React state.