← React & TypeScript

React con TypeScript

¿Cómo tipas los eventos en React con TypeScript? Por ejemplo, el `onChange` de un input o el `onSubmit` de un formulario.

Ver respuesta — intenta responderla en voz alta primero

React trae sus propios tipos de evento genéricos: ChangeEvent<HTMLInputElement>, FormEvent<HTMLFormElement>, MouseEvent<HTMLButtonElement>, etc. El genérico indica sobre qué elemento HTML ocurre el evento.

React no usa los eventos nativos del navegador directamente: los envuelve en "synthetic events" para que funcionen igual en todos los navegadores. Por eso los tipos no salen del DOM, sino del paquete de React: los importas desde "react".

  • El patrón es TipoDeEvento<ElementoHTML>: el primer nombre dice qué pasó (change, submit, click) y el genérico dice sobre qué elemento.
  • Los más comunes: ChangeEvent<HTMLInputElement> para inputs, FormEvent<HTMLFormElement> para el submit de un formulario y MouseEvent<HTMLButtonElement> para clics en botones.
  • Truco para no memorizarlos: escribe el handler inline (onChange={(e) => ...}) y TypeScript infiere el tipo solo. Luego, si quieres extraer la función, pasa el cursor sobre e y copia el tipo que te muestra el editor.
  • e.currentTarget es el elemento donde pusiste el handler y siempre está bien tipado según el genérico. e.target es donde se originó el evento (puede ser un hijo), así que su tipo es más genérico. En un input con onChange puedes usar e.target.value sin problema; en contenedores, prefiere 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 es string, bien tipado
  }

  function manejarEnvio(e: FormEvent<HTMLFormElement>) {
    e.preventDefault(); // evita recargar la página
    console.log("Enviando:", correo);
  }

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

  return (
    <form onSubmit={manejarEnvio}>
      <input type="email" value={correo} onChange={manejarCambio} />
      {/* Truco: si el handler va inline, TypeScript infiere el tipo de e solito */}
      <button type="button" name="limpiar" onClick={manejarLimpiar}>Limpiar</button>
      <button type="submit">Enviar</button>
    </form>
  );
}

Tipar el evento con el tipo nativo del navegador o con any:

// El tipo nativo del DOM no encaja con el synthetic event de React
function manejarCambio(e: Event) {
  setCorreo(e.target.value);
  // Error: Property 'value' does not exist on type 'EventTarget'.
}

La corrección es usar el tipo de React con el elemento correcto:

import type { ChangeEvent } from "react";

function manejarCambio(e: ChangeEvent<HTMLInputElement>) {
  setCorreo(e.target.value); // ahora TypeScript sabe que value existe
}

"React envuelve los eventos nativos en synthetic events, así que uso los tipos que exporta React: ChangeEvent<HTMLInputElement> para inputs, FormEvent<HTMLFormElement> para formularios y MouseEvent<HTMLButtonElement> para clics. El genérico indica el elemento donde va el handler. Si no recuerdo el nombre exacto, escribo el handler inline y TypeScript lo infiere; luego copio ese tipo si quiero extraer la función. También cuido la diferencia entre target, que es donde se originó el evento, y currentTarget, que es el elemento con el handler y siempre queda bien tipado."

Reto rápido

Crea un componente Buscador con un input controlado y un formulario. Al enviar, debe hacer preventDefault e imprimir el texto buscado. Tipa los dos handlers como funciones separadas (no inline).

Ver solución
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>
  );
}