Conceptos Básicos de Eventos en React
11 ejemplos para comenzar con Eventos en React: 7 básicos y 4 intermedios.
Busca en todas las páginas de la documentación
11 ejemplos para comenzar con Eventos en React: 7 básicos y 4 intermedios.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
No se requieren paquetes adicionales: cada manejador en esta página viene con React. Un proyecto React estándar (Next.js, Vite o CRA) es suficiente.
Dos convenciones aplican a cada ejemplo:
onClick, onChange, onKeyDown - no en minúsculas.Event (e.target, e.preventDefault(), etc.).Los manejadores de eventos se ejecutan en el cliente. En un proyecto Next.js App Router, colócalos dentro de un componente marcado "use client" (o importa ese componente desde un Componente del Servidor).
Responde a una pulsación de botón con un manejador de clic.
"use client";
export default function ClickCounter() {
const handleClick = () => {
alert("¡Se presionó el botón!");
};
return <button onClick={handleClick}>Haz clic en mí</button>;
}onClick={handleClick}), no una llamada de función (onClick={handleClick()}).React.MouseEvent con clientX, clientY, shiftKey, etc.onClick de un padre también se dispara a menos que un hijo llame a e.stopPropagation().<button type="button"> dentro de un <form> a menos que realmente quieras que envíe el formulario.Relacionado: Eventos del Ratón -- clic, desplazamiento, arrastrar, menú contextual | Eventos (Fundamentos) -- descripción general de eventos sintéticos | Tipificación de Eventos --
MouseEvent<HTMLButtonElement>y similares
Captura pulsaciones de teclas con una entrada controlada.
"use client";
import { useState } from "react";
export default function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Tu nombre"
/>
<p>Hola, {name || "extraño"}!</p>
</div>
);
}onChange se dispara en cada pulsación de tecla - a diferencia del change del DOM nativo, que se dispara solo al perder el foco.e.target.value es siempre una cadena; analiza números/booleanos explícitamente.value en el estado de React - tienes control total sobre la validación y la transformación.react-hook-form para evitar un re-renderizado por pulsación de tecla.Relacionado: Eventos de Formulario -- onChange, onInput, onSubmit | Formularios (Fundamentos) -- controlado vs. no controlado | React Hook Form -- escalabilidad de formularios más allá de algunos campos
Responde a pulsaciones de teclas, incluidos modificadores y combinaciones de teclas.
"use client";
import { useState } from "react";
export default function SearchBox() {
const [query, setQuery] = useState("");
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
console.log("Buscar:", query);
} else if (e.key === "Escape") {
setQuery("");
}
};
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Buscar..."
/>
);
}e.key (p. ej., "Enter", "Escape", "ArrowUp") - no el e.keyCode obsoleto.e.ctrlKey, e.metaKey, e.shiftKey, e.altKey - útil para atajos de teclado.useEffect con window.addEventListener("keydown", ...).onKeyDown se dispara antes de que se inserte el carácter; onKeyUp se dispara después de soltar.Relacionado: Eventos de Teclado -- atajos de teclado, composición IME, accesibilidad | useKeyboardShortcut -- gancho de atajo global reutilizable
Rastrrea cuándo un elemento gana o pierde el foco - para validación, tooltips y estado de la interfaz.
"use client";
import { useState } from "react";
export default function EmailField() {
const [email, setEmail] = useState("");
const [touched, setTouched] = useState(false);
const showError = touched && !email.includes("@");
return (
<div>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
onBlur={() => setTouched(true)}
/>
{showError && <p role="alert">Por favor, ingresa un correo válido.</p>}
</div>
);
}onFocus se dispara cuando el elemento recibe el foco; onBlur cuando lo pierde.focus/blur nativo. Úsalos en contenedores, no solo en entradas.onBlur antes de mostrar errores de validación para que los usuarios no sean asustados a mitad de la escritura.relatedTarget nativo en el evento.Relacionado: Eventos de Foco -- onFocus, onBlur, onFocusIn | Accesibilidad de Formularios -- patrones ARIA alrededor del foco
Construye interacciones de desplazamiento sin los problemas del mouseover nativo.
"use client";
import { useState } from "react";
export default function Tooltip() {
const [open, setOpen] = useState(false);
return (
<span
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
style={{ position: "relative" }}
>
Desplázate sobre mí
{open && (
<span style={{ position: "absolute", top: "-1.5rem", left: 0 }}>
¡Tooltip!
</span>
)}
</span>
);
}onMouseEnter/onMouseLeave se disparan una vez al entrar/salir del elemento - no se disparan para elementos secundarios, a diferencia de onMouseOver/onMouseOut.onPointerEnter/onPointerLeave para entrada unificada.:hover para estilos puramente visuales - reserva manejadores de JS para comportamiento.Relacionado: Eventos del Ratón -- API completa del ratón | Eventos de Puntero -- ratón + toque + bolígrafo unificados | Componente Tooltip -- tooltip listo para producción
Maneja el envío de formulario sin una recarga completa de página.
"use client";
import { useState, type FormEvent } from "react";
export default function ContactForm() {
const [email, setEmail] = useState("");
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("Enviar:", email);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button type="submit">Enviar</button>
</form>
);
}e.preventDefault() para detener la recarga de página predeterminada del navegador.onSubmit al <form>, no al botón de envío - se dispara en clics de botón y pulsaciones de Enter.<form action={serverAction}> en su lugar y omite onSubmit.type="submit" real para que los usuarios de teclado puedan enviar con Enter.Relacionado: Eventos de Formulario -- onSubmit, onReset, onInvalid | Formularios de Acciones del Servidor -- flujo de formulario React 19 nativo
Intercepta operaciones del portapapeles para transformar o bloquear el contenido.
"use client";
export default function ProtectedText() {
const handleCopy = (e: React.ClipboardEvent<HTMLDivElement>) => {
e.preventDefault();
e.clipboardData.setData("text/plain", "La copia está deshabilitada aquí.");
};
const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
const pasted = e.clipboardData.getData("text");
console.log("Pegado:", pasted);
};
return (
<div onCopy={handleCopy}>
<p>Intenta copiar este texto.</p>
<input onPaste={handlePaste} placeholder="Pega aquí" />
</div>
);
}e.clipboardData expone getData (para pegar) y setData (para copiar/cortar).e.preventDefault() antes de setData para reemplazar el contenido del portapapeles predeterminado.navigator.clipboard.writeText() en su lugar.Relacionado: Eventos del Portapapeles -- API completa de onCopy/onCut/onPaste | useCopyToClipboard -- gancho de botón "copiar" reutilizable
Maneja ratón, toque y bolígrafo con un único conjunto de manejadores - sin bifurcación por tipo de entrada.
"use client";
import { useState } from "react";
export default function Draggable() {
const [pos, setPos] = useState({ x: 0, y: 0 });
const [dragging, setDragging] = useState(false);
const handlePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
e.currentTarget.setPointerCapture(e.pointerId);
setDragging(true);
};
const handlePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (dragging) setPos({ x: e.clientX, y: e.clientY });
};
const handlePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
e.currentTarget.releasePointerCapture(e.pointerId);
setDragging(false);
};
return (
<div
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
style={{
position: "absolute",
left: pos.x,
top: pos.y,
width: 60,
height: 60,
background: dragging ? "crimson" : "steelblue",
touchAction: "none",
}}
/>
);
}setPointerCapture(pointerId) sigue recibiendo eventos pointermove incluso si el cursor sale del elemento.touch-action: none en CSS para que el navegador no capture el gesto para desplazamiento/zoom.e.pointerType ("mouse", "touch", "pen") solo cuando la interacción realmente necesita bifurcarse.Relacionado: Eventos de Puntero -- captura, eventos coalescidos, presión del bolígrafo | Eventos de Toque -- cuándo aún llegar a APIs específicas de toque | Eventos del Ratón -- contrapartida heredada
Carga más contenido cuando el usuario llega al final de un contenedor desplazable.
"use client";
import { useState } from "react";
export default function FeedList({ initial }: { initial: string[] }) {
const [items, setItems] = useState(initial);
const [loading, setLoading] = useState(false);
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
const el = e.currentTarget;
const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 100;
if (atBottom && !loading) {
setLoading(true);
fetch(`/api/feed?after=${items.length}`)
.then((r) => r.json())
.then((next: string[]) => setItems((prev) => [...prev, ...next]))
.finally(() => setLoading(false));
}
};
return (
<div onScroll={handleScroll} style={{ height: 400, overflow: "auto" }}>
{items.map((item) => (
<p key={item}>{item}</p>
))}
{loading && <p>Cargando más...</p>}
</div>
);
}onScroll se dispara muy frecuentemente - protégete contra doble carga con una bandera loading o un debounce.scrollHeight - scrollTop - clientHeight en lugar de igualdad exacta para activar un poco antes.IntersectionObserver en un elemento centinela - se dispara solo cuando el centinela realmente entra en vista.onScroll a window vía React - usa useEffect + window.addEventListener("scroll", ...) para desplazamiento a nivel de página.Relacionado: Eventos de Desplazamiento -- throttling, encabezados pegajosos, restauración de desplazamiento | useIntersectionObserver -- patrón preferido para "cargar más"
Implementa arrastrar y soltar HTML5 entre dos listas usando eventos de arrastrar sintéticos de React.
"use client";
import { useState } from "react";
export default function Board() {
const [todo, setTodo] = useState(["Escribir documentos", "Enviar función"]);
const [done, setDone] = useState<string[]>([]);
const handleDragStart = (e: React.DragEvent<HTMLLIElement>, item: string) => {
e.dataTransfer.setData("text/plain", item);
};
const handleDrop = (e: React.DragEvent<HTMLUListElement>) => {
e.preventDefault();
const item = e.dataTransfer.getData("text/plain");
setTodo((prev) => prev.filter((i) => i !== item));
setDone((prev) => [...prev, item]);
};
return (
<div style={{ display: "flex", gap: "2rem" }}>
<ul>
{todo.map((item) => (
<li key={item} draggable onDragStart={(e) => handleDragStart(e, item)}>
{item}
</li>
))}
</ul>
<ul onDragOver={(e) => e.preventDefault()} onDrop={handleDrop}>
<strong>Hecho</strong>
{done.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}e.preventDefault() en onDragOver o el destino de la suelta rechazará la suelta.e.dataTransfer.setData(type, value) y léela con getData - este es el único puente seguro entre ventanas.dnd-kit o similar.draggable al elemento fuente; React no lo establece automáticamente.Relacionado: Eventos de Arrastrar y Soltar -- onDragStart, onDragOver, onDrop | dnd-kit -- arrastrar y soltar ergonómico con soporte táctil
Rastrrea cuándo un vídeo está listo para reproducirse y cuándo termina.
"use client";
import { useRef, useState } from "react";
export default function VideoPlayer({ src }: { src: string }) {
const ref = useRef<HTMLVideoElement>(null);
const [status, setStatus] = useState<"loading" | "ready" | "ended">("loading");
return (
<div>
<video
ref={ref}
src={src}
controls
onLoadedMetadata={() => setStatus("ready")}
onEnded={() => setStatus("ended")}
/>
<p>Estado: {status}</p>
{status === "ended" && (
<button onClick={() => ref.current?.play()}>Reproducir de nuevo</button>
)}
</div>
);
}onLoadedMetadata se dispara una vez que el navegador conoce la duración y dimensiones del vídeo - es seguro leer ref.current.duration.onEnded se dispara cuando la reproducción alcanza el final; onPlay, onPause, onTimeUpdate cubren el resto del ciclo de vida.<video> o <audio>.onTransitionEnd y onAnimationEnd te dan la misma señal de finalización.Relacionado: Eventos de Medios y Animación -- referencia completa de eventos de medios + animación | useRef -- acceso al nodo DOM subyacente
Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥