Las Mejores Prácticas de React Events
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de cada página en esta sección.
- preventDefault Antes que setData en Copy: Llamar a
e.clipboardData.setData(...)enonCopysine.preventDefault()permite que el navegador sobrescriba silenciosamente tu payload personalizado con la selección; siempre previene el predeterminado primero, luego escribe cada formato que quieras (p. ej.,text/plainytext/html). - Haz Que los Objetivos de Paste No-Input Reciban Foco:
<div>y otros elementos que no son de entrada nunca recibenonPastea menos que tengan foco, así que agregatabIndex={0}(o-1) más un estilo de foco seguro para contorno para que las zonas de paste personalizado realmente funcionen. - preventDefault en dragover para Opt-In a Drop: Un elemento no es un objetivo de drop válido hasta que
onDragOverllama ae.preventDefault(); sin él el cursor muestra "no permitido" yonDropnunca se dispara, que es el error HTML5 drag más común. - Lee dataTransfer Solo en dragstart/drop: Por seguridad,
dataTransfer.getData()devuelve""durantedragover/dragenter; solodataTransfer.typeses inspeccionar durante el drag, así que feature-gate en types y lee el payload enonDrop. - No Usar Native DnD en Mobile: El drag-and-drop HTML5 está efectivamente roto en navegadores móviles, así que cambia a una librería como
dnd-kito construye lógica de pointer-event personalizada cuando el soporte touch importa. - Detecta Real Focus Exit Con contains: Usa
e.currentTarget.contains(e.relatedTarget as Node)para diferenciar "el foco se movió entre children" de "el foco realmente salió del contenedor" —onBlur={e => { if (!e.currentTarget.contains(e.relatedTarget)) closeDropdown() }}— recuerda querelatedTargetesnullcuando el foco sale del documento completamente. - React Focus Events Bubble: A diferencia de
focus/blurnativo,onFocusyonBlurde React están cableados afocusin/focusouty hacen burbuja hacia arriba en el árbol — así que un padre ve cada cambio de foco de hijo, y mezclar manejadores React conaddEventListener("focus", …)produce un comportamiento inconsistente. - Usa onMouseDown para Click-After-Blur: Cuando un dropdown se descarta en
onBlurantes de que un clic en su opción se registre, cambia el manejador de la opción aonMouseDown+e.preventDefault()para que el clic sea capturado antes de que el foco se mueva. - onChange Se Dispara en Cada Pulsación de Tecla:
onChangede React se asigna al evento nativoinput, no achangenativo, así que se dispara en cada pulsación de tecla en lugar de en blur; usaonBlur(ouseDeferredValue) cuando genuinamente quieres semántica de commit con debounce. - File Inputs Son Siempre No Controlados: Los navegadores prohíben establecer
valueen<input type="file">, así que lee archivos a través deonChange—onChange={e => setFile(e.target.files?.[0])}— y un ref, y nunca intentes controlar el valor; limpiar significa asignar""al nodo del DOM directamente víainputRef.current!.value = "". - No Mezcles action Con onSubmit:
actionprop del formulario de React 19 másuseActionStateevitaonSubmitcompletamente (sin necesidad depreventDefault); combinar los dos en el mismo formulario produce un double-handling confuso, así que elige un modelo por formulario. - Usa onKeyDown, No onKeyPress:
onKeyPressestá deprecated y nunca se dispara para Escape, arrows, Tab, o modificadores; usaonKeyDownpara cada interacción de teclado incluyendo atajos y navegación de widget accesible. - e.key vs e.code:
e.keyes consciente del layout y modificador ("A"con Shift) y es lo que quieres para atajos;e.codees la posición física ("KeyA") y es lo que quieres para juegos al estilo WASD donde el layout no debe importar. - Protégete Contra e.repeat:
onKeyDownse dispara continuamente mientras una tecla se mantiene presionada cone.repeat === true, así que early-return en repeat —onKeyDown={e => { if (e.repeat) return; sendMessage() }}— para manejadores costosos como llamadas de red o transiciones de estado que solo quieres una vez por pulsación. - Filtra onTransitionEnd por propertyName:
onTransitionEndse dispara una vez por cada propiedad CSS animada, así que una transición enopacityytransformse dispara dos veces; protege manejadores:onTransitionEnd={e => { if (e.propertyName !== "opacity") return; onFadeComplete() }}. - No Uses onTimeUpdate para Frame-Accurate UI: Los navegadores throttle
onTimeUpdatea aproximadamente cuatro veces por segundo, que es demasiado grueso para scrubs de progreso o highlights de waveform — dirígelos desderequestAnimationFrameleyendovideo.currentTimeen su lugar. - onMouseEnter/Leave No Bubble:
onMouseEnter/onMouseLeavese disparan solo en el elemento exacto, mientrasonMouseOver/onMouseOuthacen burbuja y se disparan nuevamente en cada cruce de hijo; mezclarlos causa flicker en regiones hover anidadas, así que elige enter/leave para UIs hover. - Throttle onMouseMove Con rAF:
onMouseMovese dispara en cada píxel de movimiento y puede desencadenar re-render storms; escribe a un ref y programa actualizaciones conrequestAnimationFrame, o acepta que las actualizaciones raw de state afectarán la frame rate. - Llama setPointerCapture Sincrónica: Para mantener un drag vivo cuando el puntero sale del elemento a alta velocidad, llámalo sincrónicamente en
onPointerDown—onPointerDown={e => e.currentTarget.setPointerCapture(e.pointerId)}— llamarlo después o desde un effect es demasiado tarde para redirigir eventos posteriores. - Establece touch-action: none para Pointer Drag: El scrolling táctil pasivo-predeterminado aún se opone a tu drag a menos que lo desactives en CSS; agrega
touch-action: noneen el elemento arrastrable para que el navegador ceda el gesto a tus manejadores pointer. - onPointerLeave Aún Se Dispara Durante Capture: La captura de puntero activo no suprime
onPointerLeave, así que usaonGotPointerCapture/onLostPointerCapturepara rastrear el estado de captura real en lugar de inferirlo de eventos enter/leave. - Page Scroll Necesita un window Listener:
onScrollen el root o body solo se dispara cuando ese elemento en sí tiene un scrollbar, así que usauseEffect—useEffect(() => { const h = () => {}; window.addEventListener("scroll", h, { passive: true }); return () => window.removeEventListener("scroll", h); }, [])— y siempre pasa{ passive: true }para mantener el scrolling de hilo compositor rápido. - Prefiere IntersectionObserver Sobre onScroll:
IntersectionObserverse ejecuta fuera del hilo principal y es mucho más barato que computar visibilidad de matemáticasonScroll; úsalo para lazy-load, infinite scroll, y lógica de estilo "scrolled past header", y protégete conisIntersectingya que se dispara inmediatamente en observe. - Usa changedTouches en onTouchEnd: En
onTouchEndlas listastouchesytargetTouchesestán vacías (el dedo que disparó el evento ya ha levantado), así que siempre leee.changedTouches[0]para obtener la posición final y convierte conArray.from(e.changedTouches)ya queTouchListno es un array. - React Touch Listeners Son Pasivos: React 17+ registra manejadores touch como pasivos en la raíz de delegación, así que
e.preventDefault()dentro deonTouchMovefalla silenciosamente; ya sea adjunta un oyente no-pasivo víaref.addEventListener("touchmove", h, { passive: false })o aplicatouch-action: noney deja que CSS bloquee el gesto.