LLevelUp
0
← Back to topic

Events & Forms

Every interactive React app is driven by events. React’s event system wraps native browser events in a consistent cross-browser interface called SyntheticEvent, letting you handle clicks, input changes, form submissions, and keyboard events with a unified API.

Synthetic Events

React registers a single event listener at the root of your app instead of attaching listeners to individual DOM nodes. When an event fires, React creates a SyntheticEvent wrapper with the same interface as a native event and dispatches it to the appropriate handler.

function ClickLogger() {
  function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
    console.log(e.target);       // The DOM node that was clicked
    console.log(e.currentTarget); // The element the handler is attached to
    console.log(e.nativeEvent);   // The underlying browser event
  }

  return <button onClick={handleClick}>Click me</button>;
}

Event handlers are passed as props (camelCase: onClick, onChange, onKeyDown). Never use strings like the old onclick="..." attribute.

Inline vs Named Handlers

// Inline arrow function — fine for simple cases
<button onClick={() => setCount(c => c + 1)}>+</button>

// Named handler — better for complex logic or when you need the event object
function handleDelete(e: React.MouseEvent, id: string) {
  e.stopPropagation(); // Prevent event from bubbling up
  deleteItem(id);
}
<button onClick={(e) => handleDelete(e, item.id)}>Delete</button>

TIP

Pass handlers by reference (onClick={handleClick}) rather than wrapping in an unnecessary arrow function (onClick={() => handleClick()}) when you don’t need to pass arguments. The wrapper creates a new function reference every render, which can affect memoized child components.

Controlled vs Uncontrolled Inputs

Uncontrolled inputs let the DOM manage their own state. You read the value when you need it (e.g., on submit) using a ref.

Controlled inputs keep the value in React state. React is always the source of truth, and the input always reflects that state.

// Controlled input
function ControlledInput() {
  const [value, setValue] = useState("");

  return (
    <input
      value={value}
      onChange={(e: React.ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}
    />
  );
}

// Uncontrolled input (accessed via ref on submit)
function UncontrolledInput() {
  const inputRef = useRef<HTMLInputElement>(null);

  function handleSubmit() {
    console.log(inputRef.current?.value);
  }

  return <input ref={inputRef} defaultValue="" />;
}

NOTE

Most React code uses controlled inputs because they make validation, formatting, and derived state straightforward. Uncontrolled inputs are useful for file inputs (which cannot be controlled) or when integrating with non-React libraries.

Building a Controlled Form

type FormState = { email: string; password: string };

function LoginForm() {
  const [form, setForm] = useState<FormState>({ email: "", password: "" });
  const [error, setError] = useState("");

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    const { name, value } = e.target;
    setForm(prev => ({ ...prev, [name]: value }));
  }

  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault(); // Prevent page reload
    if (!form.email.includes("@")) {
      setError("Please enter a valid email.");
      return;
    }
    submitLogin(form);
  }

  return (
    <form onSubmit={handleSubmit}>
      {error && <p className="error">{error}</p>}
      <input name="email" value={form.email} onChange={handleChange} type="email" />
      <input name="password" value={form.password} onChange={handleChange} type="password" />
      <button type="submit">Log in</button>
    </form>
  );
}

The e.preventDefault() call on form submission is essential — without it the browser navigates to the form’s action URL, reloading the page.

Common Events Quick Reference

Event PropTriggered When
onClickElement is clicked
onChangeInput value changes
onSubmitForm is submitted
onFocus / onBlurElement gains / loses focus
onKeyDown / onKeyUpKeyboard key is pressed / released
onMouseEnter / onMouseLeaveMouse enters / leaves element

CAUTION

onChange in React fires on every keystroke, not just when the input loses focus (unlike the native DOM change event). This is intentional — it keeps React state in sync with every character typed.

Further Learning

Search these terms to go deeper:

  • “React SyntheticEvent react.dev” — complete list of supported events and their TypeScript types
  • “controlled vs uncontrolled components react.dev” — when to choose each approach
  • “React Hook Form library” — the most popular library for handling forms without the controlled-input boilerplate
  • “e.stopPropagation vs e.preventDefault React” — understanding event bubbling and default behavior
  • “Josh W. Comeau forms in React” — practical guide with real-world form patterns