v19.2Latest

Puede que no necesites un Efecto

Los Efectos son una vía de escape del paradigma de React. Te permiten "salir" de React y sincronizar tus componentes con algún sistema externo, como un widget que no es de React, la red o el DOM del navegador. Si no hay un sistema externo involucrado (por ejemplo, si quieres actualizar el estado de un componente cuando cambian algunas props o el estado), no deberías necesitar un Efecto. Eliminar Efectos innecesarios hará que tu código sea más fácil de seguir, se ejecute más rápido y sea menos propenso a errores.

Aprenderás
  • Por qué y cómo eliminar Efectos innecesarios de tus componentes
  • Cómo almacenar en caché cálculos costosos sin Efectos
  • Cómo restablecer y ajustar el estado del componente sin Efectos
  • Cómo compartir lógica entre manejadores de eventos
  • Qué lógica debe moverse a los manejadores de eventos
  • Cómo notificar a los componentes padres sobre los cambios

Cómo eliminar Efectos innecesarios

Hay dos casos comunes en los que no necesitas Efectos:

  • No necesitas Efectos para transformar datos para el renderizado.Por ejemplo, supongamos que quieres filtrar una lista antes de mostrarla. Podrías sentirte tentado a escribir un Efecto que actualice una variable de estado cuando la lista cambie. Sin embargo, esto es ineficiente. Cuando actualizas el estado, React primero llamará a las funciones de tus componentes para calcular lo que debería estar en la pantalla. Luego React"confirmará"estos cambios en el DOM, actualizando la pantalla. Luego React ejecutará tus Efectos. Si tu Efectotambiénactualiza inmediatamente el estado, ¡esto reinicia todo el proceso desde cero! Para evitar los pases de renderizado innecesarios, transforma todos los datos en el nivel superior de tus componentes. Ese código se volverá a ejecutar automáticamente cada vez que cambien tus props o estado.
  • No necesitas Efectos para manejar eventos de usuario.Por ejemplo, supongamos que quieres enviar una solicitud POST a/api/buyy mostrar una notificación cuando el usuario compre un producto. En el manejador de eventos del clic del botón Comprar, sabes exactamente qué sucedió. Para cuando se ejecuta un Efecto, no sabesquéhizo el usuario (por ejemplo, qué botón se hizo clic). Por eso normalmente manejarás los eventos de usuario en los manejadores de eventos correspondientes.

necesitasEfectos parasincronizarcon sistemas externos. Por ejemplo, puedes escribir un Efecto que mantenga sincronizado un widget de jQuery con el estado de React. También puedes obtener datos con Efectos: por ejemplo, puedes sincronizar los resultados de búsqueda con la consulta de búsqueda actual. Ten en cuenta que losframeworksmodernos proporcionan mecanismos de obtención de datos integrados más eficientes que escribir Efectos directamente en tus componentes.

Para ayudarte a adquirir la intuición correcta, ¡veamos algunos ejemplos concretos comunes!

Actualizar el estado basado en props o estado

Supón que tienes un componente con dos variables de estado:firstName y lastName. Quieres calcular unfullNamea partir de ellas concatenándolas. Además, te gustaría quefullNamese actualice cada vez quefirstName o lastNamecambien. Tu primer instinto podría ser añadir una variable de estadofullNamey actualizarla en un Efecto:

Esto es más complicado de lo necesario. También es ineficiente: realiza un pase de renderizado completo con un valor obsoleto parafullName, y luego vuelve a renderizar inmediatamente con el valor actualizado. Elimina la variable de estado y el Efecto:

Cuando algo se puede calcular a partir de las props o el estado existentes,no lo pongas en el estado.En su lugar, calcúlalo durante el renderizado.Esto hace que tu código sea más rápido (evitas las actualizaciones "en cascada" adicionales), más simple (eliminas código) y menos propenso a errores (evitas errores causados por variables de estado que se desincronizan entre sí). Si este enfoque te resulta nuevo,Pensando en Reactexplica qué debe ir en el estado.

Almacenamiento en caché de cálculos costosos

Este componente calculavisibleTodostomando lostodosque recibe por props y filtrándolos según la propfilter. Podrías sentir la tentación de almacenar el resultado en el estado y actualizarlo desde un Efecto:

Como en el ejemplo anterior, esto es innecesario e ineficiente. Primero, elimina el estado y el Efecto:

Por lo general, ¡este código está bien! Pero quizásgetFilteredTodos()sea lenta o tengas muchostodos. En ese caso, no querrás recalculargetFilteredTodos()si alguna variable de estado no relacionada, comonewTodo, ha cambiado.

Puedes almacenar en caché (o"memorizar") un cálculo costoso envolviéndolo en un HookuseMemo:

Nota

React Compilerpuede memorizar automáticamente cálculos costosos por ti, eliminando la necesidad de usaruseMemomanualmente en muchos casos.

O, escrito en una sola línea:

Esto le indica a React que no quieres que la función interna se vuelva a ejecutar a menos quetodos o filterhayan cambiado.React recordará el valor de retorno degetFilteredTodos()durante el renderizado inicial. Durante los siguientes renderizados, verificará sitodos o filterson diferentes. Si son iguales a la última vez,useMemodevolverá el último resultado que ha almacenado. Pero si son diferentes, React llamará a la función interna nuevamente (y almacenará su resultado).

La función que envuelves enuseMemose ejecuta durante el renderizado, por lo que esto solo funciona paracálculos puros.

Deep Dive
¿Cómo saber si un cálculo es costoso?

Restablecer todo el estado cuando cambia una propiedad

Este componenteProfilePagerecibe una propiedaduserId. La página contiene un campo de entrada para comentarios, y usas una variable de estadocommentpara almacenar su valor. Un día, notas un problema: cuando navegas de un perfil a otro, el estadocommentno se restablece. Como resultado, es fácil publicar accidentalmente un comentario en el perfil de un usuario equivocado. Para solucionar el problema, quieres limpiar la variable de estadocommentcada vez que cambia la propiedaduserId:

Esto es ineficiente porqueProfilePagey sus hijos primero se renderizarán con el valor obsoleto, y luego se renderizarán de nuevo. También es complicado porque tendrías que hacer esto encadacomponente que tenga algún estado dentro deProfilePage. Por ejemplo, si la interfaz de usuario del comentario está anidada, también querrías limpiar el estado del comentario anidado.

En su lugar, puedes indicarle a React que el perfil de cada usuario es conceptualmente un perfildiferentedándole una clave explícita. Divide tu componente en dos y pasa un atributokeydesde el componente externo al interno:

Normalmente, React preserva el estado cuando el mismo componente se renderiza en el mismo lugar.Al pasaruserIdcomo unakeyal componenteProfile, le estás pidiendo a React que trate dos componentesProfilecon diferentesuserIdcomo dos componentes diferentes que no deben compartir ningún estado.Cada vez que la clave (que has establecido comouserId) cambia, React recreará el DOM yreiniciará el estadodel componenteProfiley todos sus hijos. Ahora el campocommentse limpiará automáticamente al navegar entre perfiles.

Ten en cuenta que en este ejemplo, solo el componente externoProfilePagese exporta y es visible para otros archivos del proyecto. Los componentes que renderizanProfilePageno necesitan pasarle la clave: le pasanuserIdcomo una prop regular. El hecho de queProfilePagelo pase como unakeyal componente internoProfilees un detalle de implementación.

Ajustar parte del estado cuando cambia una prop

A veces, es posible que quieras reiniciar o ajustar una parte del estado cuando cambia una prop, pero no todo.

EsteListcomponente recibe una lista deitemscomo prop, y mantiene el elemento seleccionado en la variable de estadoselection. Quieres restablecer laselection a nullcada vez que la propitemsrecibe un array diferente:

Esto tampoco es ideal. Cada vez que lositemscambian, elListy sus componentes hijos se renderizarán inicialmente con un valor deselectionobsoleto. Luego React actualizará el DOM y ejecutará los Efectos. Finalmente, la llamadasetSelection(null)provocará otra re-renderización delListy sus componentes hijos, reiniciando todo este proceso de nuevo.

Comienza eliminando el Efecto. En su lugar, ajusta el estado directamente durante el renderizado:

Almacenar información de renderizados anterioresde esta manera puede ser difícil de entender, pero es mejor que actualizar el mismo estado en un Efecto. En el ejemplo anterior,setSelectionse llama directamente durante un renderizado. React volverá a renderizar elListinmediatamentedespués de que salga con una sentenciareturn. React aún no ha renderizado los hijos delListni actualizado el DOM, por lo que esto permite que los hijos delListomitan renderizar el valor obsoleto deselection.

Cuando actualizas un componente durante el renderizado, React descarta el JSX devuelto e inmediatamente reintenta el renderizado. Para evitar reintentos en cascada muy lentos, React solo te permite actualizar el estado delmismocomponente durante un renderizado. Si actualizas el estado de otro componente durante un renderizado, verás un error. Una condición comoitems !== prevItemses necesaria para evitar bucles. Puedes ajustar el estado de esta manera, pero cualquier otro efecto secundario (como cambiar el DOM o establecer timeouts) debe permanecer en manejadores de eventos o Efectos paramantener los componentes puros.

Aunque este patrón es más eficiente que un Efecto, la mayoría de los componentes tampoco deberían necesitarlo.No importa cómo lo hagas, ajustar el estado en función de props u otro estado hace que tu flujo de datos sea más difícil de entender y depurar. Siempre verifica si puedesreiniciar todo el estado con una clave o calcular todo durante el renderizadoen su lugar. Por ejemplo, en lugar de almacenar (y reiniciar) elelementoseleccionado, puedes almacenar elID del elemento seleccionado:

Ahora ya no es necesario "ajustar" el estado en absoluto. Si el elemento con el ID seleccionado está en la lista, permanece seleccionado. Si no lo está, laselectioncalculada durante el renderizado seránullporque no se encontró ningún elemento coincidente. Este comportamiento es diferente, pero podría decirse que es mejor porque la mayoría de los cambios enitemspreservan la selección.

Compartir lógica entre manejadores de eventos

Supongamos que tienes una página de producto con dos botones (Comprar y Pagar) que permiten comprar ese producto. Quieres mostrar una notificación cada vez que el usuario añade el producto al carrito. Llamar ashowNotification()en los manejadores de clic de ambos botones parece repetitivo, por lo que podrías sentirte tentado a colocar esta lógica en un Efecto:

Este Efecto es innecesario. También es muy probable que cause errores. Por ejemplo, supongamos que tu aplicación "recuerda" el carrito de compras entre recargas de página. Si añades un producto al carrito una vez y recargas la página, la notificación aparecerá de nuevo. Seguirá apareciendo cada vez que recargues la página de ese producto. Esto se debe a queproduct.isInCartya serátrueal cargar la página, por lo que el Efecto anterior llamará ashowNotification().

Cuando no estés seguro de si un código debe estar en un Efecto o en un manejador de eventos, pregúntatepor quéeste código necesita ejecutarse. Usa Efectos solo para código que deba ejecutarseporqueel componente se mostró al usuario.En este ejemplo, la notificación debería aparecer porque el usuariopresionó el botón, ¡no porque la página se mostró! Elimina el Efecto y coloca la lógica compartida en una función llamada desde ambos manejadores de eventos:

Esto elimina el Efecto innecesario y corrige el error.

Enviar una solicitud POST

Este componenteFormenvía dos tipos de solicitudes POST. Envía un evento de análisis cuando se monta. Cuando completas el formulario y haces clic en el botón Enviar, enviará una solicitud POST al endpoint/api/register:

Apliquemos los mismos criterios que en el ejemplo anterior.

La solicitud POST de análisis debe permanecer en un Efecto. Esto se debe a que larazónpara enviar el evento de análisis es que el formulario se mostró. (Se dispararía dos veces en desarrollo, peroconsulta aquípara saber cómo lidiar con eso).

Sin embargo, la solicitud POST a/api/registerno es causada por el hecho de que el formulario semuestre. Solo quieres enviar la solicitud en un momento específico: cuando el usuario presiona el botón. Solo debería ocurriren esa interacción particular. Elimina el segundo Efecto y mueve esa solicitud POST al manejador de eventos:

Cuando decidas si colocar cierta lógica en un manejador de eventos o en un Efecto, la pregunta principal que debes responder esqué tipo de lógicaes desde la perspectiva del usuario. Si esta lógica es causada por una interacción particular, mantenla en el manejador de eventos. Si es causada por el usuarioviendoel componente en la pantalla, mantenla en el Efecto.

Cadenas de cálculos

A veces podrías sentirte tentado a encadenar Efectos que ajustan cada uno una parte del estado basándose en otro estado:

Este código tiene dos problemas.

El primer problema es que es muy ineficiente: el componente (y sus hijos) tienen que volver a renderizarse entre cada llamadaseten la cadena. En el ejemplo anterior, en el peor de los casos (setCard→ renderizado →setGoldCardCount→ renderizado →setRound→ renderizado →setIsGameOver→ renderizado) hay tres renderizados innecesarios del árbol inferior.

El segundo problema es que, incluso si no fuera lento, a medida que tu código evoluciona, te encontrarás con casos en los que la "cadena" que escribiste no se ajusta a los nuevos requisitos. Imagina que estás añadiendo una forma de recorrer el historial de movimientos del juego. Lo harías actualizando cada variable de estado a un valor del pasado. Sin embargo, establecer el estadocarda un valor del pasado volvería a desencadenar la cadena de Efectos y cambiaría los datos que estás mostrando. Ese tipo de código suele ser rígido y frágil.

En este caso, es mejor calcular lo que puedas durante el renderizado y ajustar el estado en el controlador de eventos:

Esto es mucho más eficiente. Además, si implementas una forma de ver el historial del juego, ahora podrás establecer cada variable de estado a un movimiento del pasado sin desencadenar la cadena de Efectos que ajusta todos los demás valores. Si necesitas reutilizar lógica entre varios controladores de eventos, puedesextraer una funcióny llamarla desde esos controladores.

Recuerda que dentro de los controladores de eventos, elestado se comporta como una instantánea.Por ejemplo, incluso después de llamar asetRound(round + 1), la variableroundreflejará el valor en el momento en que el usuario hizo clic en el botón. Si necesitas usar el siguiente valor para los cálculos, defínelo manualmente comoconst nextRound = round + 1.

En algunos casos,no puedescalcular el siguiente estado directamente en el controlador de eventos. Por ejemplo, imagina un formulario con múltiples menús desplegables donde las opciones del siguiente menú dependen del valor seleccionado del menú anterior. Entonces, una cadena de Efectos es apropiada porque estás sincronizando con la red.

Inicializar la aplicación

Alguna lógica debería ejecutarse solo una vez cuando la aplicación se carga.

Podrías sentirte tentado a colocarla en un Efecto en el componente de nivel superior:

Sin embargo, descubrirás rápidamente quese ejecuta dos veces en desarrollo.Esto puede causar problemas—por ejemplo, tal vez invalide el token de autenticación porque la función no fue diseñada para ser llamada dos veces. En general, tus componentes deben ser resistentes a ser remontados. Esto incluye tu componente de nivel superiorApp.

Aunque en la práctica en producción nunca pueda ser remontado, seguir las mismas restricciones en todos los componentes facilita mover y reutilizar código. Si alguna lógica debe ejecutarseuna vez por carga de la aplicaciónen lugar deuna vez por montaje del componente, agrega una variable de nivel superior para rastrear si ya se ha ejecutado:

También puedes ejecutarlo durante la inicialización del módulo y antes de que la aplicación se renderice:

El código en el nivel superior se ejecuta una vez cuando tu componente es importado—incluso si finalmente no se renderiza. Para evitar ralentizaciones o comportamientos sorprendentes al importar componentes arbitrarios, no abuses de este patrón. Mantén la lógica de inicialización de toda la aplicación en módulos de componentes raíz comoApp.jso en el punto de entrada de tu aplicación.

Notificar a los componentes padres sobre cambios de estado

Supongamos que estás escribiendo un componenteTogglecon un estado internoisOnque puede sertrue o false. Hay varias formas diferentes de alternarlo (haciendo clic o arrastrando). Quieres notificar al componente padre cada vez que cambia el estado interno delToggle, así que expones un eventoonChangey lo llamas desde un Efecto:

Como antes, esto no es ideal. ElToggleactualiza primero su estado, y React actualiza la pantalla. Luego React ejecuta el Efecto, que llama a la funciónonChangepasada desde un componente padre. Ahora el componente padre actualizará su propio estado, iniciando otra pasada de renderizado. Sería mejor hacer todo en una sola pasada.

Elimina el Efecto y en su lugar actualiza el estado deamboscomponentes dentro del mismo manejador de eventos:

Con este enfoque, tanto el componenteTogglecomo su componente padre actualizan su estado durante el evento. Reactagrupa las actualizacionesde diferentes componentes juntas, por lo que solo habrá una pasada de renderizado.

También podrías eliminar el estado por completo y, en su lugar, recibirisOndesde el componente padre:

"Elevar el estado"permite que el componente padre controle completamente elTogglealternando el propio estado del padre. Esto significa que el componente padre tendrá que contener más lógica, pero habrá menos estado en general del que preocuparse. ¡Siempre que intentes mantener sincronizadas dos variables de estado diferentes, intenta elevar el estado en su lugar!

Pasar datos al padre

Este componenteChildobtiene algunos datos y luego los pasa al componenteParenten un Efecto:

En React, los datos fluyen desde los componentes padres hacia sus hijos. Cuando ves algo incorrecto en la pantalla, puedes rastrear de dónde viene la información subiendo por la cadena de componentes hasta encontrar qué componente pasa la prop incorrecta o tiene el estado equivocado. Cuando los componentes hijos actualizan el estado de sus componentes padres en Efectos, el flujo de datos se vuelve muy difícil de rastrear. Dado que tanto el hijo como el padre necesitan los mismos datos, deja que el componente padre obtenga esos datos ylos pase hacia abajoal hijo en su lugar:

Esto es más simple y mantiene el flujo de datos predecible: los datos fluyen hacia abajo desde el padre hacia el hijo.

Suscribirse a un almacén externo

A veces, tus componentes pueden necesitar suscribirse a algunos datos fuera del estado de React. Estos datos podrían provenir de una biblioteca de terceros o de una API integrada del navegador. Dado que estos datos pueden cambiar sin que React lo sepa, necesitas suscribir manualmente tus componentes a ellos. Esto a menudo se hace con un Efecto, por ejemplo:

Aquí, el componente se suscribe a un almacén de datos externo (en este caso, la API del navegadornavigator.onLine). Dado que esta API no existe en el servidor (por lo que no se puede usar para el HTML inicial), inicialmente el estado se establece entrue. Siempre que el valor de ese almacén de datos cambia en el navegador, el componente actualiza su estado.

Aunque es común usar Efectos para esto, React tiene un Hook específico para suscribirse a un almacén externo que se prefiere en su lugar. Elimina el Efecto y reemplázalo con una llamada auseSyncExternalStore:

Este enfoque es menos propenso a errores que sincronizar manualmente datos mutables con el estado de React mediante un Efecto. Normalmente, escribirás un Hook personalizado comouseOnlineStatus()anteriormente para no tener que repetir este código en los componentes individuales.Lee más sobre cómo suscribirse a almacenes externos desde componentes de React.

Obtención de datos

Muchas aplicaciones usan Efectos para iniciar la obtención de datos. Es bastante común escribir un Efecto de obtención de datos como este:

Noes necesariomover esta solicitud de datos a un controlador de eventos.

Esto podría parecer una contradicción con los ejemplos anteriores donde necesitabas poner la lógica en los controladores de eventos. Sin embargo, considera que no esel evento de escriturala razón principal para realizar la solicitud. Las entradas de búsqueda a menudo se precargan desde la URL, y el usuario podría navegar hacia Atrás y Adelante sin tocar la entrada.

No importa de dónde venganpage y query. Mientras este componente esté visible, quieres mantener losresultssincronizadoscon los datos de la red para lapagey elqueryactuales. Por eso es un Efecto.

Sin embargo, el código anterior tiene un error. Imagina que escribes"hello"rápidamente. Entonces elquerycambiará de"h", a"he","hel","hell", y"hello". Esto desencadenará solicitudes separadas, pero no hay garantía sobre el orden en que llegarán las respuestas. Por ejemplo, la respuesta de"hell"puede llegardespuésde la respuesta de"hello". Dado que llamará asetResults()al final, estarás mostrando los resultados de búsqueda incorrectos. Esto se llama una“condición de carrera”: dos solicitudes diferentes “compitieron” entre sí y llegaron en un orden diferente al que esperabas.

Para corregir la condición de carrera, necesitasagregar una función de limpiezapara ignorar las respuestas obsoletas:

Esto asegura que cuando tu Efecto obtenga datos, se ignorarán todas las respuestas excepto la última solicitada.

Manejar condiciones de carrera no es la única dificultad al implementar la obtención de datos. También podrías considerar el almacenamiento en caché de las respuestas (para que el usuario pueda hacer clic en Atrás y ver la pantalla anterior al instante), cómo obtener datos en el servidor (para que el HTML renderizado inicialmente en el servidor contenga el contenido obtenido en lugar de un indicador de carga), y cómo evitar cascadas de red (para que un componente hijo pueda obtener datos sin esperar a cada componente padre).

Estos problemas se aplican a cualquier biblioteca de UI, no solo a React. Resolverlos no es trivial, por eso losframeworksmodernos proporcionan mecanismos de obtención de datos integrados más eficientes que obtener datos en Efectos.

Si no usas un framework (y no quieres construir el tuyo) pero te gustaría hacer que la obtención de datos desde Efectos sea más ergonómica, considera extraer tu lógica de obtención en un Hook personalizado como en este ejemplo:

Probablemente también quieras añadir algo de lógica para el manejo de errores y para rastrear si el contenido se está cargando. Puedes construir un Hook como este tú mismo o usar una de las muchas soluciones ya disponibles en el ecosistema de React.Aunque esto por sí solo no será tan eficiente como usar el mecanismo de obtención de datos integrado de un framework, mover la lógica de obtención de datos a un Hook personalizado facilitará adoptar una estrategia de obtención de datos eficiente más adelante.

En general, siempre que tengas que recurrir a escribir Efectos, presta atención a cuándo puedes extraer una funcionalidad en un Hook personalizado con una API más declarativa y específica comouseDataanteriormente. Cuantas menos llamadas directas auseEffecttengas en tus componentes, más fácil te resultará mantener tu aplicación.

Recapitulación

  • Si puedes calcular algo durante el renderizado, no necesitas un Efecto.
  • Para almacenar en caché cálculos costosos, añadeuseMemoen lugar deuseEffect.
  • Para restablecer el estado de un árbol de componentes completo, pásale unakeydiferente.
  • Para restablecer una parte específica del estado en respuesta a un cambio de prop, configúralo durante el renderizado.
  • El código que se ejecuta porque un componente fuemostradodebe estar en Efectos, el resto debe estar en eventos.
  • Si necesitas actualizar el estado de varios componentes, es mejor hacerlo durante un único evento.
  • Siempre que intentes sincronizar variables de estado en diferentes componentes, considera elevar el estado.
  • Puedes obtener datos con Efectos, pero necesitas implementar limpieza para evitar condiciones de carrera.

Try out some challenges

Challenge 1 of 4:Transform data without Effects #

The TodoList below displays a list of todos. When the “Show only active todos” checkbox is ticked, completed todos are not displayed in the list. Regardless of which todos are visible, the footer displays the count of todos that are not yet completed.

Simplify this component by removing all the unnecessary state and Effects.