React
Eventos en React
Explica el sistema de eventos de React, handlers, propagación, preventDefault, callbacks con argumentos y separación entre interacción y renderizado.
- Última actualización
- Actualizada
- Nivel
- Fundamentos
React
Explica el sistema de eventos de React, handlers, propagación, preventDefault, callbacks con argumentos y separación entre interacción y renderizado.
Los eventos conectan acciones concretas del usuario o del navegador con cambios de estado y operaciones externas. Un event handler pertenece a la interacción que lo dispara; no forma parte del cálculo puro del render.
Un handler es una función que React ejecuta cuando ocurre un evento asociado:
function SaveButton() {
function handleClick() {
console.log("Guardando");
}
return (
<button type="button" onClick={handleClick}>
Guardar
</button>
);
}Debes pasar la función, no ejecutar su resultado durante render:
<button type="button" onClick={handleClick}>Guardar</button><button type="button" onClick={handleClick()}>Guardar</button> // IncorrectoEn la segunda versión, handleClick se ejecuta cada vez que el componente renderiza. La prop onClick recibe después su valor de retorno, no el handler.
El render debe permanecer puro: calcula interfaz a partir de props, state y context. Sin handlers, una acción como guardar, eliminar o abrir un panel tendría que ejecutarse durante render o depender de una mutación externa difícil de rastrear.
render
└── describe botones y formularios
interacción posterior
└── ejecuta handler
├── actualiza estado
├── llama un servicio
└── provoca un nuevo renderEl handler crea una frontera temporal clara entre describir la interfaz y responder a una acción.
Un evento responde a una causa concreta:
function handleSubmit() {
createOrder();
}Un Effect sincroniza el componente con un sistema externo porque el componente está montado o porque cambiaron dependencias:
useEffect(() => {
const connection = connect(roomId);
return () => connection.disconnect();
}, [roomId]);No uses un Effect para trabajo que pertenece claramente a un clic o envío. Hacerlo pierde la relación causal y puede repetir operaciones cuando cambien dependencias o durante comprobaciones de desarrollo.
Cuando el handler necesita datos concretos, crea una función que los capture:
function ProductRow({ product }: { product: Product }) {
return (
<button type="button" onClick={() => removeProduct(product.id)}>
Eliminar {product.name}
</button>
);
}La función se crea durante render, pero no se ejecuta hasta el clic. Crear funciones locales es normal; solo necesitas memoizarlas cuando exista un motivo verificable.
También puedes definir un handler nombrado:
function ProductRow({ product }: { product: Product }) {
function handleDelete() {
removeProduct(product.id);
}
return <button onClick={handleDelete}>Eliminar</button>;
}La versión nombrada suele ser más fácil de ampliar, probar con lectura y depurar.
React entrega información sobre el evento:
function SearchField() {
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
console.log(event.currentTarget.value);
}
return <input type="search" onChange={handleChange} />;
}currentTarget representa el elemento cuyo handler se está ejecutando. target representa el origen inicial del evento y puede ser un descendiente.
function Toolbar() {
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
console.log(event.currentTarget.name);
}
return <button name="save" onClick={handleClick}>Guardar</button>;
}Con TypeScript, especificar el tipo del elemento mejora la información disponible y evita casts innecesarios.
Muchos eventos se propagan desde el elemento objetivo hacia ancestros que también tienen handlers.
function ProductCard() {
return (
<article onClick={() => openDetails()}>
<h2>Teclado</h2>
<button
type="button"
onClick={(event) => {
event.stopPropagation();
removeProduct();
}}
>
Eliminar
</button>
</article>
);
}Sin stopPropagation, un clic en Eliminar también ejecutaría el handler del artículo.
Sin embargo, detener propagación no debe convertirse en un parche constante. Un article completo con onClick puede ser una estructura accesible y semánticamente débil. A menudo es mejor ofrecer un enlace o botón explícito para abrir detalles.
El navegador tiene una fase de captura y otra de propagación hacia arriba. React permite escuchar captura con props como onClickCapture:
<div onClickCapture={() => logInteraction()}>
<button onClick={handleSave}>Guardar</button>
</div>La captura puede ser útil para logging o coordinación especial, pero añade complejidad. Prefiere handlers locales y explícitos para el comportamiento normal.
Algunos eventos activan comportamientos propios del navegador. Enviar un formulario puede navegar o recargar la página:
function LoginForm() {
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
// validar y enviar
}
return <form onSubmit={handleSubmit}>...</form>;
}preventDefault evita el comportamiento predeterminado; no detiene la propagación. stopPropagation evita que el evento continúe hacia ancestros; no cancela automáticamente la acción del navegador.
No canceles comportamiento nativo sin reemplazarlo con una experiencia equivalente y accesible.
La lógica de envío debe asociarse al form, no únicamente al clic de un botón:
<form onSubmit={handleSubmit}>
<input name="email" type="email" required />
<button type="submit">Ingresar</button>
</form>Así funciona el envío mediante teclado, tecnologías de asistencia y distintos controles submit. Usar solo onClick en el botón puede omitir estas rutas.
Un componente puede exponer eventos de dominio:
type ConfirmDialogProps = {
onConfirm: () => void;
onCancel: () => void;
};
function ConfirmDialog({ onConfirm, onCancel }: ConfirmDialogProps) {
return (
<div role="dialog" aria-modal="true">
<button type="button" onClick={onCancel}>Cancelar</button>
<button type="button" onClick={onConfirm}>Confirmar</button>
</div>
);
}El componente comunica qué ocurrió sin conocer la implementación del padre.
Convención útil:
handleConfirm.onConfirm.Esto diferencia quién maneja el evento y quién lo expone.
Los handlers pueden solicitar cambios de estado:
function Counter() {
const [count, setCount] = useState(0);
function handleIncrement() {
setCount((current) => current + 1);
}
return <button onClick={handleIncrement}>{count}</button>;
}El setter programa una actualización. No modifica inmediatamente la variable count del handler actual; ese valor pertenece al render que creó la función. El siguiente render recibirá el nuevo estado.
function SaveButton() {
const [status, setStatus] = useState<"idle" | "saving" | "error">("idle");
async function handleSave() {
if (status === "saving") return;
setStatus("saving");
try {
await saveChanges();
setStatus("idle");
} catch {
setStatus("error");
}
}
return (
<button
type="button"
onClick={handleSave}
disabled={status === "saving"}
aria-busy={status === "saving"}
>
{status === "saving" ? "Guardando…" : "Guardar"}
</button>
);
}saving.En producción también debes considerar cancelación, desmontaje, mensajes recuperables y consistencia con el servidor.
Prefiere elementos nativos:
<button type="button" onClick={handleOpen}>Abrir</button>No reemplaces un botón con:
<div onClick={handleOpen}>Abrir</div>El div no recibe automáticamente foco, activación con Enter o Space, semántica ni estados disabled. Recrear todo eso manualmente es más complejo y propenso a errores.
Para navegación usa enlaces; para acciones usa botones; para envío usa formularios y controles submit.
Deshabilita o deduplica operaciones que no deben ejecutarse varias veces. El servidor también debe manejar idempotencia cuando corresponda.
La operación externa puede terminar después. Diseña la capa de datos para cancelar, ignorar resultados obsoletos o mantener la operación fuera del componente cuando sea necesario.
Una acción puede cambiar el servidor pero fallar la respuesta. Evita asumir que todo error de red significa que la operación no ocurrió; considera reconsulta o claves idempotentes.
El handler captura props y state del render en que fue creado. Usa actualizadores funcionales o rediseña la fuente de datos cuando necesites el valor más reciente.
Los eventos de React siguen el árbol React, incluso cuando un portal cambia la ubicación DOM. Esto puede sorprender en modales y overlays.
preventDefault con stopPropagation.onClick.preventDefault y stopPropagation resuelven problemas diferentes.submit.div no tiene.onClick={handleSave()} ejecuta la acción demasiado pronto?target y currentTarget?onSubmit?stopPropagation y cuándo revisarías la estructura?count no cambia inmediatamente después de llamar setCount?target es el origen inicial; currentTarget es el elemento cuyo handler se ejecuta.Listas y keys explica cómo React relaciona elementos repetidos entre renders y por qué una identidad incorrecta puede mover estado al elemento equivocado.