← React & TypeScript

React with TypeScript

How do you type events in React with TypeScript? For example, the `onChange` of an input or the `onSubmit` of a form.

Show answer — try answering out loud first

React ships its own generic event types: ChangeEvent<HTMLInputElement>, FormEvent<HTMLFormElement>, MouseEvent<HTMLButtonElement>, etc. The generic indicates which HTML element the event happens on.

React doesn't use the browser's native events directly: it wraps them in "synthetic events" so they work the same across all browsers. That's why the types don't come from the DOM, but from React's package: you import them from "react".

  • The pattern is EventType<HTMLElement>: the first name says what happened (change, submit, click) and the generic says on which element.
  • The most common ones: ChangeEvent<HTMLInputElement> for inputs, FormEvent<HTMLFormElement> for a form's submit, and MouseEvent<HTMLButtonElement> for clicks on buttons.
  • Trick to avoid memorizing them: write the handler inline (onChange={(e) => ...}) and TypeScript infers the type on its own. Then, if you want to extract the function, hover over e and copy the type the editor shows you.
  • e.currentTarget is the element where you attached the handler and is always properly typed according to the generic. e.target is where the event originated (it can be a child), so its type is more generic. On an input with onChange you can use e.target.value without a problem; on containers, prefer currentTarget.
import { useState } from "react";
import type { ChangeEvent, FormEvent, MouseEvent } from "react";

function FormularioContacto() {
  const [correo, setCorreo] = useState("");

  function manejarCambio(e: ChangeEvent<HTMLInputElement>) {
    setCorreo(e.target.value); // value is string, properly typed
  }

  function manejarEnvio(e: FormEvent<HTMLFormElement>) {
    e.preventDefault(); // prevents reloading the page
    console.log("Enviando:", correo);
  }

  function manejarLimpiar(e: MouseEvent<HTMLButtonElement>) {
    console.log("Botón:", e.currentTarget.name); // currentTarget properly typed
    setCorreo("");
  }

  return (
    <form onSubmit={manejarEnvio}>
      <input type="email" value={correo} onChange={manejarCambio} />
      {/* Trick: if the handler is inline, TypeScript infers the type of e on its own */}
      <button type="button" name="limpiar" onClick={manejarLimpiar}>Limpiar</button>
      <button type="submit">Enviar</button>
    </form>
  );
}

Typing the event with the browser's native type or with any:

// The native DOM type doesn't match React's synthetic event
function manejarCambio(e: Event) {
  setCorreo(e.target.value);
  // Error: Property 'value' does not exist on type 'EventTarget'.
}

The fix is to use React's type with the correct element:

import type { ChangeEvent } from "react";

function manejarCambio(e: ChangeEvent<HTMLInputElement>) {
  setCorreo(e.target.value); // now TypeScript knows that value exists
}

"React wraps native events in synthetic events, so I use the types React exports: ChangeEvent<HTMLInputElement> for inputs, FormEvent<HTMLFormElement> for forms, and MouseEvent<HTMLButtonElement> for clicks. The generic indicates the element where the handler goes. If I don't remember the exact name, I write the handler inline and TypeScript infers it; then I copy that type if I want to extract the function. I also mind the difference between target, which is where the event originated, and currentTarget, which is the element with the handler and is always properly typed."

Quick challenge

Create a Buscador component with a controlled input and a form. On submit, it should do preventDefault and print the searched text. Type the two handlers as separate functions (not inline).

See solution
import { useState } from "react";
import type { ChangeEvent, FormEvent } from "react";

function Buscador() {
  const [texto, setTexto] = useState("");

  function manejarCambio(e: ChangeEvent<HTMLInputElement>) {
    setTexto(e.target.value);
  }

  function manejarEnvio(e: FormEvent<HTMLFormElement>) {
    e.preventDefault();
    console.log("Buscando:", texto);
  }

  return (
    <form onSubmit={manejarEnvio}>
      <input value={texto} onChange={manejarCambio} placeholder="Buscar..." />
      <button type="submit">Buscar</button>
    </form>
  );
}