Skip to content
TypeScript ts react 3 min read

Event Types

Every interactive React component handles events: typing into an input, submitting a form, clicking a button. React ships its own synthetic event types that wrap the DOM events with consistent cross-browser behavior, and typing your handlers with them unlocks autocomplete on event.target, event.key, event.currentTarget, and friends. The trick is knowing which event type goes with which interaction and which element. This page covers ChangeEvent, FormEvent, MouseEvent, and the rest, plus when to skip the annotations entirely by letting JSX infer them.

The two ways to type a handler

You can annotate the event parameter directly, or annotate the whole function with the matching EventHandler type. The second form lets inference type the parameter for you.

import type { ChangeEvent, ChangeEventHandler } from "react";

// Style 1 — annotate the event parameter:
function handleChange(event: ChangeEvent<HTMLInputElement>) {
  console.log(event.target.value);
}

// Style 2 — annotate the function; event is inferred:
const handleChange2: ChangeEventHandler<HTMLInputElement> = (event) => {
  console.log(event.target.value);
};

Both produce the same checked handler. Reach for the …EventHandler<T> alias when assigning to a variable, and the explicit parameter type when declaring a standalone function.

Change events on inputs

ChangeEvent<T> types onChange for form controls. The element type parameter decides what event.target is — use HTMLInputElement, HTMLSelectElement, or HTMLTextAreaElement.

import { useState, type ChangeEvent } from "react";

function SearchBox() {
  const [query, setQuery] = useState("");

  function onChange(event: ChangeEvent<HTMLInputElement>) {
    setQuery(event.target.value); // value is typed as string
  }

  return <input value={query} onChange={onChange} />;
}

Because event.target is an HTMLInputElement, .value is a string and properties like .checked exist only when the element is a checkbox. Picking the wrong element type (e.g. HTMLDivElement) makes .value disappear.

Form submission

FormEvent<T> types the onSubmit handler. Call event.preventDefault() to stop the browser’s full-page reload, then read the controlled values from state.

import type { FormEvent } from "react";

function LoginForm() {
  function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    console.log(data.get("email"));
  }

  return (
    <form onSubmit={onSubmit}>
      <input name="email" />
      <button type="submit">Sign in</button>
    </form>
  );
}

event.currentTarget is typed as HTMLFormElement (the element the handler is attached to), which is what lets new FormData(event.currentTarget) type-check.

Prefer event.currentTarget over event.target for the element the handler is bound to. currentTarget is precisely typed; target is the broader EventTarget because the event may have originated on a descendant.

Mouse, keyboard, and focus events

Each interaction has its own event type, all generic over the element. The most common ones:

import type { MouseEvent, KeyboardEvent, FocusEvent } from "react";

function onClick(event: MouseEvent<HTMLButtonElement>) {
  console.log(event.clientX, event.shiftKey);
}

function onKeyDown(event: KeyboardEvent<HTMLInputElement>) {
  if (event.key === "Enter") event.currentTarget.blur();
}

function onBlur(event: FocusEvent<HTMLInputElement>) {
  console.log(event.target.value);
}
InteractionEvent typeElement parameter
onChangeChangeEvent<T>input / select / textarea
onSubmitFormEvent<T>HTMLFormElement
onClickMouseEvent<T>HTMLButtonElement, etc.
onKeyDownKeyboardEvent<T>the focused element
onFocus / onBlurFocusEvent<T>the focused element

Let JSX infer inline handlers

For handlers written inline in JSX, you usually do not need any annotation. React knows onClick on a <button> provides a MouseEvent<HTMLButtonElement>, so the parameter is inferred.

function Toolbar() {
  return (
    <button
      onClick={(event) => {
        // event: MouseEvent<HTMLButtonElement> — fully inferred
        event.currentTarget.disabled = true;
      }}
    >
      Save
    </button>
  );
}

This contextual typing only works inline. Once a handler is extracted into a named function, the parameter has no context to infer from, so you must annotate it explicitly.

Best Practices

  • Use React’s synthetic event types (ChangeEvent, FormEvent, MouseEvent), not the global DOM ones.
  • Always pass the element type parameter so event.target/currentTarget is precise.
  • Prefer event.currentTarget for the handler’s own element; it is more strictly typed than target.
  • Let inline JSX handlers infer their event type; annotate only extracted functions.
  • Use the …EventHandler<T> aliases when assigning a handler to a typed variable.
  • Call event.preventDefault() inside FormEvent handlers on controlled forms.
  • Import event types with import type to keep them out of the runtime bundle.
Last updated June 29, 2026
Was this helpful?