Eliminar dependencias de Efectos
Cuando escribes un Efecto, el linter verificará que hayas incluido cada valor reactivo (como props y estado) que el Efecto lee en la lista de dependencias de tu Efecto. Esto asegura que tu Efecto permanezca sincronizado con las props y el estado más recientes de tu componente. Las dependencias innecesarias pueden hacer que tu Efecto se ejecute con demasiada frecuencia, o incluso crear un bucle infinito. Sigue esta guía para revisar y eliminar dependencias innecesarias de tus Efectos.
Aprenderás
- Cómo solucionar bucles infinitos de dependencias en Efectos
- Qué hacer cuando quieres eliminar una dependencia
- Cómo leer un valor de tu Efecto sin "reaccionar" a él
- Cómo y por qué evitar dependencias de objetos y funciones
- Por qué suprimir el linter de dependencias es peligroso y qué hacer en su lugar
Las dependencias deben coincidir con el código
Cuando escribes un Efecto, primero especificas cómoiniciar y detenerlo que quieres que haga tu Efecto:
Luego, si dejas las dependencias del Efecto vacías ([]), el linter sugerirá las dependencias correctas:
Rellénalas según lo que diga el linter:
Los Efectos "reaccionan" a los valores reactivos.Dado queroomIdes un valor reactivo (puede cambiar debido a un nuevo renderizado), el linter verifica que lo hayas especificado como dependencia. SiroomIdrecibe un valor diferente, React volverá a sincronizar tu Efecto. Esto asegura que el chat permanezca conectado a la sala seleccionada y "reaccione" al menú desplegable:
Para eliminar una dependencia, demuestra que no es una dependencia
Observa que no puedes "elegir" las dependencias de tu Efecto. Cadavalor reactivoutilizado por el código de tu Efecto debe declararse en tu lista de dependencias. La lista de dependencias está determinada por el código circundante:
Los valores reactivosincluyen props y todas las variables y funciones declaradas directamente dentro de tu componente. Dado queroomIdes un valor reactivo, no puedes eliminarlo de la lista de dependencias. El linter no lo permitiría:
¡Y el linter tendría razón! Dado queroomIdpuede cambiar con el tiempo, esto introduciría un error en tu código.
Para eliminar una dependencia, "demuestra" al linter queno necesitaser una dependencia.Por ejemplo, puedes sacarroomIdde tu componente para demostrar que no es reactivo y no cambiará en los re-renderizados:
Ahora queroomIdno es un valor reactivo (y no puede cambiar en un re-renderizado), no necesita ser una dependencia:
Por eso ahora podrías especificar unalista de dependencias vacía ([]).Tu Efectorealmente ya nodepende de ningún valor reactivo, así querealmente nonecesita volver a ejecutarse cuando cambien las props o el estado del componente.
Para cambiar las dependencias, cambia el código
Es posible que hayas notado un patrón en tu flujo de trabajo:
- Primero,cambias el códigode tu Efecto o cómo se declaran tus valores reactivos.
- Luego, sigues el linter y ajustas las dependencias paraque coincidan con el código que has cambiado.
- Si no estás satisfecho con la lista de dependencias,vuelves al primer paso(y cambias el código de nuevo).
La última parte es importante.Si quieres cambiar las dependencias, primero cambia el código circundante.Puedes pensar en la lista de dependencias comouna lista de todos los valores reactivos utilizados por el código de tu Efecto.Tú noeligesqué poner en esa lista. La listadescribetu código. Para cambiar la lista de dependencias, cambia el código.
Esto puede sentirse como resolver una ecuación. Puedes empezar con un objetivo (por ejemplo, eliminar una dependencia) y necesitas "encontrar" el código que coincida con ese objetivo. No a todo el mundo le divierte resolver ecuaciones, ¡y lo mismo se puede decir sobre escribir Efectos! Afortunadamente, hay una lista de recetas comunes que puedes probar a continuación.
Pitfall
Si tienes una base de código existente, es posible que tengas algunos Efectos que suprimen el linter así:
Cuando las dependencias no coinciden con el código, existe un riesgo muy alto de introducir errores.Al suprimir el linter, "mientes" a React sobre los valores de los que depende tu Efecto.
En su lugar, utiliza las técnicas que se describen a continuación.
Eliminar dependencias innecesarias
Cada vez que ajustas las dependencias del Efecto para reflejar el código, mira la lista de dependencias. ¿Tiene sentido que el Efecto se vuelva a ejecutar cuando cambie cualquiera de estas dependencias? A veces, la respuesta es "no":
- Es posible que quieras volver a ejecutardiferentes partesde tu Efecto bajo diferentes condiciones.
- Es posible que solo quieras leer elvalor más recientede alguna dependencia en lugar de “reaccionar” a sus cambios.
- Una dependencia puede cambiar con demasiada frecuenciainvoluntariamenteporque es un objeto o una función.
Para encontrar la solución correcta, necesitarás responder algunas preguntas sobre tu Efecto. Vamos a repasarlas.
¿Debería este código moverse a un controlador de eventos?
Lo primero que debes pensar es si este código debería ser un Efecto en absoluto.
Imagina un formulario. Al enviarlo, estableces la variable de estadosubmitted en true. Necesitas enviar una solicitud POST y mostrar una notificación. Has colocado esta lógica dentro de un Efecto que “reacciona” a quesubmittedseatrue:
Más tarde, quieres estilizar el mensaje de notificación según el tema actual, así que lees el tema actual. Dado quethemese declara en el cuerpo del componente, es un valor reactivo, por lo que lo agregas como una dependencia:
Al hacer esto, has introducido un error. Imagina que primero envías el formulario y luego cambias entre los temas Oscuro y Claro. Elthemecambiará, el Efecto se volverá a ejecutar y, por lo tanto, mostrará la misma notificación nuevamente.
El problema aquí es que esto no debería ser un Efecto en primer lugar.Quieres enviar esta solicitud POST y mostrar la notificación en respuesta aenviar el formulario,que es una interacción particular. Para ejecutar algún código en respuesta a una interacción particular, coloca esa lógica directamente en el controlador de eventos correspondiente:
Ahora que el código está en un controlador de eventos, no es reactivo, por lo que solo se ejecutará cuando el usuario envíe el formulario. Lee más sobreelegir entre controladores de eventos y Efectos y cómo eliminar Efectos innecesarios.
¿Tu Efecto está haciendo varias cosas no relacionadas?
La siguiente pregunta que debes hacerte es si tu Efecto está haciendo varias cosas no relacionadas.
Imagina que estás creando un formulario de envío donde el usuario necesita elegir su ciudad y área. Obtienes la lista decitiesdel servidor según elcountryseleccionado para mostrarlas en un menú desplegable:
Este es un buen ejemplo deobtención de datos en un Efecto.Estás sincronizando el estadocitiescon la red según la propiedadcountry. No puedes hacer esto en un controlador de eventos porque necesitas obtener los datos tan pronto como se muestreShippingFormy cada vez que cambie la propiedadcountry(sin importar qué interacción la cause).
Ahora digamos que estás agregando un segundo cuadro de selección para las áreas de la ciudad, que debería obtener lasareas para la cityactualmente seleccionada. Podrías comenzar agregando una segunda llamadafetchpara la lista de áreas dentro del mismo Efecto:
Sin embargo, dado que el Efecto ahora utiliza la variable de estadocity, has tenido que agregarcitya la lista de dependencias. Esto, a su vez, introdujo un problema: cuando el usuario selecciona una ciudad diferente, el Efecto se volverá a ejecutar y llamará afetchCities(country). Como resultado, estarás volviendo a obtener innecesariamente la lista de ciudades muchas veces.
El problema con este código es que estás sincronizando dos cosas diferentes sin relación:
- Quieres sincronizar el estado
citiescon la red en función de la propiedadcountry. - Quieres sincronizar el estado
areascon la red en función del estadocity.
Divide la lógica en dos Efectos, cada uno de los cuales reacciona a la propiedad con la que necesita sincronizarse:
Ahora el primer Efecto solo se vuelve a ejecutar si cambiacountry, mientras que el segundo Efecto se vuelve a ejecutar cuando cambiacity. Los has separado por propósito: dos cosas diferentes se sincronizan mediante dos Efectos separados. Dos Efectos separados tienen dos listas de dependencias separadas, por lo que no se activarán mutuamente de forma involuntaria.
El código final es más largo que el original, pero dividir estos Efectos sigue siendo correcto.Cada Efecto debe representar un proceso de sincronización independiente.En este ejemplo, eliminar un Efecto no rompe la lógica del otro Efecto. Esto significa quesincronizan cosas diferentes,y es bueno separarlos. Si te preocupa la duplicación, puedes mejorar este códigoextrayendo la lógica repetitiva en un Hook personalizado.
¿Estás leyendo algún estado para calcular el siguiente estado?
Este Efecto actualiza la variable de estadomessagescon un arreglo recién creado cada vez que llega un nuevo mensaje:
Utiliza la variablemessagesparacrear un nuevo arregloque comienza con todos los mensajes existentes y agrega el nuevo mensaje al final. Sin embargo, dado quemessageses un valor reactivo leído por un Efecto, debe ser una dependencia:
Y hacer quemessagessea una dependencia introduce un problema.
Cada vez que recibes un mensaje,setMessages()hace que el componente se vuelva a renderizar con un nuevo arreglomessagesque incluye el mensaje recibido. Sin embargo, dado que este Efecto ahora depende demessages, estotambiénvolverá a sincronizar el Efecto. Así que cada nuevo mensaje hará que el chat se vuelva a conectar. ¡Al usuario no le gustaría eso!
Para solucionar el problema, no leasmessagesdentro del Efecto. En su lugar, pasa unafunción de actualización a setMessages:
Observa cómo tu Efecto ya no lee la variablemessagesen absoluto.Solo necesitas pasar una función actualizadora comomsgs => [...msgs, receivedMessage]. Reactpone tu función actualizadora en una colay proporcionará el argumentomsgsdurante el siguiente renderizado. Por eso el Efecto en sí ya no necesita depender demessages. Como resultado de esta corrección, recibir un mensaje de chat ya no hará que el chat se reconecte.
¿Quieres leer un valor sin "reaccionar" a sus cambios?
Supón que quieres reproducir un sonido cuando el usuario recibe un nuevo mensaje, a menos queisMutedseatrue:
Como tu Efecto ahora usaisMuteden su código, tienes que agregarlo a las dependencias:
El problema es que cada vez queisMutedcambia (por ejemplo, cuando el usuario presiona el botón de "Silenciar"), el Efecto se resincronizará y se reconectará al chat. ¡Esta no es la experiencia de usuario deseada! (En este ejemplo, incluso deshabilitar el linter no funcionaría—si lo haces,isMutedquedaría "atascado" con su valor anterior).
Para resolver este problema, necesitas extraer la lógica que no debería ser reactiva fuera del Efecto. No quieres que este Efecto "reaccione" a los cambios enisMuted.Mueve esta parte no reactiva de la lógica a un Evento de Efecto:
Los Eventos de Efecto te permiten dividir un Efecto en partes reactivas (que deberían "reaccionar" a valores reactivos comoroomIdy sus cambios) y partes no reactivas (que solo leen sus últimos valores, comoonMessageleeisMuted).Ahora que leesisMuteddentro de un Evento de Efecto, no necesita ser una dependencia de tu Efecto.Como resultado, el chat no se reconectará cuando actives y desactives la configuración "Silenciado", ¡resolviendo el problema original!
Envolviendo un manejador de eventos de las props
Puedes encontrarte con un problema similar cuando tu componente recibe un manejador de eventos como prop:
Supón que el componente padre pasa una funcióndiferenteonReceiveMessageen cada renderizado:
ComoonReceiveMessagees una dependencia, haría que el Efecto se resincronizara después de cada re-renderizado del padre. Esto haría que se reconectara al chat. Para resolver esto, envuelve la llamada en un Evento de Efecto:
Los Eventos de Efecto no son reactivos, por lo que no necesitas especificarlos como dependencias. Como resultado, el chat ya no se reconectará incluso si el componente padre pasa una función diferente en cada re-renderizado.
Separando código reactivo y no reactivo
En este ejemplo, quieres registrar una visita cada vez queroomIdcambia. Quieres incluir elnotificationCountactual con cada registro, peronoquieres que un cambio ennotificationCountdesencadene un evento de registro.
La solución es nuevamente separar el código no reactivo en un Evento de Efecto:
Quieres que tu lógica sea reactiva con respecto aroomId, así que leesroomIddentro de tu Efecto. Sin embargo, no quieres que un cambio ennotificationCountregistre una visita extra, así que leesnotificationCountdentro del Evento de Efecto.Aprende más sobre cómo leer las props y el estado más recientes desde Efectos usando Eventos de Efecto.
¿Cambia algún valor reactivo de forma no intencionada?
A veces,síquieres que tu Efecto "reaccione" a un cierto valor, pero ese valor cambia con más frecuencia de la que te gustaría—y puede que no refleje ningún cambio real desde la perspectiva del usuario. Por ejemplo, supongamos que creas un objetooptionsen el cuerpo de tu componente, y luego lees ese objeto desde dentro de tu Efecto:
Este objeto se declara en el cuerpo del componente, por lo que es unvalor reactivo.Cuando lees un valor reactivo como este dentro de un Efecto, lo declaras como una dependencia. Esto asegura que tu Efecto "reaccione" a sus cambios:
¡Es importante declararlo como una dependencia! Esto asegura, por ejemplo, que siroomIdcambia, tu Efecto se volverá a conectar al chat con las nuevasoptions. Sin embargo, también hay un problema con el código anterior. Para verlo, intenta escribir en la entrada del sandbox a continuación y observa lo que sucede en la consola:
En el sandbox anterior, la entrada solo actualiza la variable de estadomessage. Desde la perspectiva del usuario, esto no debería afectar la conexión del chat. Sin embargo, cada vez que actualizas elmessage, tu componente se vuelve a renderizar. Cuando tu componente se vuelve a renderizar, el código dentro de él se ejecuta nuevamente desde cero.
Un nuevo objetooptionsse crea desde cero en cada re-renderizado del componenteChatRoom. React ve que el objetooptionses unobjeto diferentedel objetooptionscreado durante el último renderizado. Por eso vuelve a sincronizar tu Efecto (que depende deoptions), y el chat se vuelve a conectar mientras escribes.
Este problema solo afecta a objetos y funciones. En JavaScript, cada objeto y función recién creados se consideran distintos de todos los demás. ¡No importa que el contenido dentro de ellos pueda ser el mismo!
Las dependencias de objetos y funciones pueden hacer que tu Efecto se resincronice más a menudo de lo necesario.
Por eso, siempre que sea posible, debes intentar evitar objetos y funciones como dependencias de tu Efecto. En su lugar, intenta moverlos fuera del componente, dentro del Efecto, o extraer valores primitivos de ellos.
Mueve objetos y funciones estáticos fuera de tu componente
Si el objeto no depende de ninguna prop o estado, puedes mover ese objeto fuera de tu componente:
De esta manera,demuestrasal linter que no es reactivo. No puede cambiar como resultado de un nuevo renderizado, por lo que no necesita ser una dependencia. Ahora, volver a renderizarChatRoomno hará que tu Efecto se resincronice.
Esto también funciona para las funciones:
Dado quecreateOptionsse declara fuera de tu componente, no es un valor reactivo. Por eso no necesita especificarse en las dependencias de tu Efecto, y por eso nunca hará que tu Efecto se resincronice.
Mueve objetos y funciones dinámicos dentro de tu Efecto
Si tu objeto depende de algún valor reactivo que pueda cambiar como resultado de un nuevo renderizado, como una proproomId, no puedes sacarlofuerade tu componente. Sin embargo, puedes mover su creacióndentrodel código de tu Efecto:
Ahora queoptionsse declara dentro de tu Efecto, ya no es una dependencia de tu Efecto. En su lugar, el único valor reactivo utilizado por tu Efecto esroomId. Dado queroomIdno es un objeto o función, puedes estar seguro de que no seráinvoluntariamentediferente. En JavaScript, los números y las cadenas se comparan por su contenido:
Gracias a esta corrección, el chat ya no se vuelve a conectar si editas la entrada:
Sin embargo,síse reconecta cuando cambias el desplegable deroomId, como era de esperar.
Esto también funciona para funciones:
Puedes escribir tus propias funciones para agrupar piezas de lógica dentro de tu Efecto. Siempre que también las declaresdentrode tu Efecto, no son valores reactivos, por lo que no necesitan ser dependencias de tu Efecto.
Lee valores primitivos de objetos
A veces, puedes recibir un objeto de las props:
El riesgo aquí es que el componente padre creará el objeto durante el renderizado:
Esto haría que tu Efecto se reconectara cada vez que el componente padre se vuelva a renderizar. Para solucionarlo, lee la información del objetofueradel Efecto, y evita tener dependencias de objetos y funciones:
La lógica se vuelve un poco repetitiva (lees algunos valores de un objeto fuera de un Efecto, y luego creas un objeto con los mismos valores dentro del Efecto). Pero hace que sea muy explícito de qué información dependerealmentetu Efecto. Si un objeto se vuelve a crear involuntariamente por el componente padre, el chat no se reconectaría. Sin embargo, sioptions.roomId o options.serverUrlrealmente son diferentes, el chat se reconectaría.
Calcula valores primitivos a partir de funciones
El mismo enfoque puede funcionar para funciones. Por ejemplo, supón que el componente padre pasa una función:
Para evitar que sea una dependencia (y que cause que se reconecte en los re-renderizados), llámala fuera del Efecto. Esto te da los valoresroomId y serverUrlque no son objetos, y que puedes leer desde dentro de tu Efecto:
Esto solo funciona para funcionespurasporque es seguro llamarlas durante el renderizado. Si tu función es un controlador de eventos, pero no quieres que sus cambios resincronicen tu Efecto,envuélvela en un Evento de Efecto en su lugar.
Recapitulación
- Las dependencias siempre deben coincidir con el código.
- Cuando no estés satisfecho con tus dependencias, lo que necesitas editar es el código.
- Suprimir el linter conduce a errores muy confusos, y siempre debes evitarlo.
- Para eliminar una dependencia, necesitas "demostrar" al linter que no es necesaria.
- Si algún código debe ejecutarse en respuesta a una interacción específica, mueve ese código a un controlador de eventos.
- Si diferentes partes de tu Efecto deben volver a ejecutarse por diferentes razones, divídelo en varios Efectos.
- Si quieres actualizar algún estado basándote en el estado anterior, pasa una función de actualización.
- Si quieres leer el último valor sin "reaccionar" a él, extrae un Evento de Efecto de tu Efecto.
- En JavaScript, los objetos y funciones se consideran diferentes si fueron creados en momentos distintos.
- Intenta evitar las dependencias de objetos y funciones. Muévelas fuera del componente o dentro del Efecto.
Try out some challenges
Challenge 1 of 4:Fix a resetting interval #
This Effect sets up an interval that ticks every second. You’ve noticed something strange happening: it seems like the interval gets destroyed and re-created every time it ticks. Fix the code so that the interval doesn’t get constantly re-created.
