Manejo de eventos
Responde a las interacciones del usuario con manejadores de eventos con seguridad de tipos.
Busca en todas las páginas de la documentación
Responde a las interacciones del usuario con manejadores de eventos con seguridad de tipos.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tarjeta de referencia rápida - lista para copiar y pegar.
// Manejador en línea
<button onClick={() => console.log("clicked")}>Haz clic aquí</button>
// Manejador con nombre
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log(e.currentTarget.name);
}
<button name="save" onClick={handleClick}>Guardar</button>
// Pasar datos a los manejadores
<button onClick={() => deleteItem(item.id)}>Eliminar</button>
// Prevenir el comportamiento por defecto
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>Cuándo recurrir a esto: Siempre que necesites responder a clics, pulsaciones de teclas, envíos de formularios, cambios de foco o cualquier otro evento del DOM.
"use client";
import { useState, useCallback } from "react";
interface LogEntry {
id: number;
type: string;
detail: string;
timestamp: string;
}
let entryId = 0;
export function EventPlayground() {
const [log, setLog] = useState<LogEntry[]>([]);
const addLog = useCallback((type: string, detail: string) => {
setLog(prev => [
{ id: ++entryId, type, detail, timestamp: new Date().toLocaleTimeString() },
...prev.slice(0, 9), // conservar los últimos 10
]);
}, []);
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
addLog("click", `Botón "${e.currentTarget.textContent}" en (${e.clientX}, ${e.clientY})`);
}
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === "Enter") {
addLog("keydown", `Enter pulsado - valor: "${e.currentTarget.value}"`);
e.currentTarget.value = "";
}
}
function handleFocus(e: React.FocusEvent<HTMLInputElement>) {
addLog("focus", `Entrada "${e.currentTarget.placeholder}" enfocada`);
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
const formData = new FormData(e.currentTarget);
addLog("submit", `Formulario enviado con nombre: "${formData.get("name")}"`);
e.currentTarget.reset();
}
return (
<div className="max-w-md space-y-4 rounded border p-4">
<div className="flex gap-2">
<button onClick={handleClick} className="rounded bg-blue-600 px-3 py-1 text-white">
Botón A
</button>
<button onClick={handleClick} className="rounded bg-green-600 px-3 py-1 text-white">
Botón B
</button>
</div>
<input
onKeyDown={handleKeyDown}
onFocus={handleFocus}
placeholder="Pulsa Enter aquí..."
className="w-full rounded border px-3 py-1"
/>
<form onSubmit={handleSubmit} className="flex gap-2">
<input
name="name"
placeholder="Tu nombre"
onFocus={handleFocus}
className="flex-1 rounded border px-3 py-1"
/>
<button type="submit" className="rounded bg-purple-600 px-3 py-1 text-white">
Enviar
</button>
</form>
<div className="max-h-48 overflow-y-auto rounded bg-gray-50 p-2">
<h3 className="mb-1 text-xs font-bold uppercase text-gray-500">Registro de eventos</h3>
{log.length === 0 && <p className="text-xs text-gray-400">Interactúa con los controles de arriba...</p>}
{log.map(entry => (
<div key={entry.id} className="border-b py-1 text-xs">
<span className="font-mono font-bold text-blue-700">{entry.type}</span>{" "}
<span className="text-gray-600">{entry.detail}</span>{" "}
<span className="text-gray-400">{entry.timestamp}</span>
</div>
))}
</div>
</div>
);
}Qué demuestra esto:
onClick, onKeyDown, onFocus, onSubmitReact.MouseEvent, React.KeyboardEvent, React.FocusEvent, React.FormEvente.preventDefault() para evitar que el envío del formulario recargue la páginae.currentTarget para acceder al elemento al que está asociado el manejadoruseCallback para estabilizar una función auxiliar que se llama con frecuenciaSyntheticEvent que normaliza las diferencias entre navegadorese.currentTarget es el elemento al que está asociado el manejador; e.target es el elemento que originó el evento (puede ser un hijo)| Prop de React | Tipo de TypeScript | Evento nativo |
|---|---|---|
onClick | React.MouseEvent<HTMLElement> | click |
onChange | React.ChangeEvent<HTMLInputElement> | input (React se dispara en cada pulsación de tecla) |
onSubmit | React.FormEvent<HTMLFormElement> | submit |
onKeyDown | React.KeyboardEvent<HTMLElement> | keydown |
onFocus | React.FocusEvent<HTMLElement> | focus |
onBlur | React.FocusEvent<HTMLElement> | blur |
onMouseEnter | React.MouseEvent<HTMLElement> | mouseenter |
onPointerDown | React.PointerEvent<HTMLElement> | pointerdown |
onDrag | React.DragEvent<HTMLElement> | drag |
Detener la propagación:
function handleInner(e: React.MouseEvent) {
e.stopPropagation(); // evita que se dispare el onClick exterior
}
<div onClick={() => console.log("outer")}>
<button onClick={handleInner}>Haz clic aquí</button>
</div>Fase de captura:
// La variante "Capture" se dispara de arriba hacia abajo antes de la fase de propagación
<div onClickCapture={() => console.log("captured first")}>
<button onClick={() => console.log("bubbled second")}>Clic</button>
</div>Acceder al evento nativo:
function handleClick(e: React.MouseEvent) {
const nativeEvent = e.nativeEvent; // el Event subyacente del DOM
console.log(nativeEvent instanceof MouseEvent); // true
}Datos personalizados mediante closures:
function handleDelete(itemId: string) {
setItems(prev => prev.filter(i => i.id !== itemId));
}
{items.map(item => (
<button key={item.id} onClick={() => handleDelete(item.id)}>
Eliminar {item.name}
</button>
))}// Tipar una prop de manejador
interface SearchBarProps {
onSearch: (query: string) => void;
}
function SearchBar({ onSearch }: SearchBarProps) {
return (
<input
type="search"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
onSearch(e.target.value);
}}
/>
);
}
// Tipo de manejador en línea (útil para props de callback)
type ClickHandler = React.MouseEventHandler<HTMLButtonElement>;
// Tipo de elemento genérico
function logEvent<T extends HTMLElement>(e: React.SyntheticEvent<T>) {
console.log(e.currentTarget.tagName);
}Llamar a la función en lugar de pasarla - onClick={handleClick()} invoca la función durante el render, no al hacer clic. Solución: Pasa la referencia: onClick={handleClick} o envuélvela en una función flecha: onClick={() => handleClick(arg)}.
Closures obsoletos en los manejadores - Un manejador de eventos creado dentro de un componente captura el estado en el momento del render. Si el estado se actualiza antes de que se ejecute el manejador (algo raro), el manejador ve valores antiguos. Solución: Usa useCallback con las dependencias correctas, o lee desde una ref para obtener los valores realmente más recientes.
onChange de React vs change del DOM - El onChange de React se dispara en cada pulsación de tecla (como el evento input del DOM), no al perder el foco como el evento change nativo. Esto sorprende a los desarrolladores que vienen de JavaScript puro. Solución: Simplemente tenlo en cuenta - es el comportamiento previsto.
Listeners de eventos pasivos - React no admite la opción { passive: true } mediante props de JSX. Para manejadores de touch/wheel donde necesitas passive: false para poder llamar a preventDefault(), puede que necesites un addEventListener manual dentro de un useEffect. Solución: Usa useEffect + ref para asociar el listener nativo.
Funciones flecha en el render - onClick={() => doSomething(id)} crea una nueva función en cada render, lo que puede anular React.memo. Solución: Para rutas críticas (listas grandes), extrae el manejador o usa useCallback. Para la mayoría de los casos está bien - no optimices prematuramente.
| Alternativa | Úsala cuando | No la uses cuando |
|---|---|---|
useEffect + addEventListener | Necesitas listeners pasivos, eventos a nivel de window o eventos que React no envuelve | El evento está en un elemento específico de React (usa un manejador de JSX) |
| Form Actions (React 19) | Manejas envíos de formularios con server actions | Formularios solo de cliente sin interacción con el servidor |
| Delegación de eventos basada en refs | Construyes una librería que necesita control directo de los eventos del DOM | Código de aplicación normal |
Un envoltorio multinavegador sobre el evento nativo del DOM. React crea un SyntheticEvent para cada manejador de eventos, normalizando las diferencias entre navegadores. Accede al evento nativo mediante e.nativeEvent si lo necesitas.
e.currentTarget - el elemento al que está asociado el manejadore.target - el elemento que realmente disparó el evento (puede ser un hijo)currentTarget cuando leas atributos del elemento al que asociaste el manejadorAñadir () invoca la función durante el render y pasa su valor de retorno como manejador. En su lugar, pasa la referencia: onClick={handleClick} o envuélvela: onClick={() => handleClick(arg)}.
Envuelve el manejador en una función flecha:
<button onClick={() => deleteItem(item.id)}>Eliminar</button>Llama a e.preventDefault() en el manejador onSubmit:
<form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>Usa los tipos de eventos de React con el genérico del elemento HTML:
function handleClick(e: React.MouseEvent<HTMLButtonElement>) { ... }
function handleChange(e: React.ChangeEvent<HTMLInputElement>) { ... }
function handleSubmit(e: React.FormEvent<HTMLFormElement>) { ... }Sí. El onChange de React se comporta como el evento input nativo, disparándose en cada pulsación de tecla - no al perder el foco como el evento change nativo. Esto es intencional para actualizaciones de formularios en tiempo real.
Llama a e.stopPropagation() en el manejador de eventos del hijo. Para la fase de captura (de arriba hacia abajo), usa onClickCapture en lugar de onClick.
En la mayoría de los casos, no. La función flecha en línea crea una nueva función en cada render, lo que puede anular React.memo en los componentes hijos. Optimiza con useCallback solo en rutas críticas como listas grandes.
Usa useEffect con addEventListener en window o document:
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.key === "Escape") closeModal();
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []);Típala como una función que acepta los datos que necesitas:
interface Props {
onSearch: (query: string) => void;
}O usa el tipo de manejador integrado de React: React.MouseEventHandler<HTMLButtonElement>.
Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥