He estado jugando con el nuevo sistema de enlace en React 16.7-alpha y me quedo atascado en un bucle infinito en useEffect cuando el estado que estoy manejando es un objeto o matriz.
Primero, uso useState y lo inicio con un objeto vacío como este:
const [obj, setObj] = useState({});
Luego, en useEffect, uso setObj para volver a establecerlo en un objeto vacío. Como segundo argumento, paso [obj], con la esperanza de que no se actualice si el contenido del objeto no ha cambiado. Pero sigue actualizándose. Supongo que porque no importa el contenido, estos siempre son objetos diferentes que hacen que React piense que sigue cambiando.
useEffect(() => {
setIngredients({});
}, [ingredients]);
Lo mismo ocurre con las matrices, pero como primitiva no se atasca en un bucle, como se esperaba.
Usando estos nuevos ganchos, ¿cómo debo manejar los objetos y la matriz al verificar si el contenido ha cambiado o no?
fuente
Respuestas:
Pasar una matriz vacía como segundo argumento para useEffect hace que solo se ejecute en mount y unmount, deteniendo así cualquier bucle infinito.
useEffect(() => { setIngredients({}); }, []);
Esto me fue aclarado en la publicación del blog sobre React hooks en https://www.robinwieruch.de/react-hooks/
fuente
Tuvo el mismo problema. No sé por qué no mencionan esto en los documentos. Solo quiero agregar un poco a la respuesta de Tobias Haugen.
Para ejecutar en todos los componentes / reenvío padre , debe usar:
useEffect(() => { // don't know where it can be used :/ })
Para ejecutar algo solo una vez después del montaje del componente (se renderizará una vez), debe usar:
useEffect(() => { // do anything only one time if you pass empty array [] // keep in mind, that component will be rendered one time (with default values) before we get here }, [] )
Para ejecutar algo una vez en el montaje de componentes y en el cambio de datos / datos2 :
const [data, setData] = useState(false) const [data2, setData2] = useState('default value for first render') useEffect(() => { // if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed // if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone). // if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this) }, [data, data2] )
Cómo lo uso la mayor parte del tiempo:
export default function Book({id}) { const [book, bookSet] = useState(false) useEffect(() => { loadBookFromServer() }, [id]) // every time id changed, new book will be loaded // Remeber that it's not always safe to omit functions from the list of dependencies. Explained in comments. async function loadBookFromServer() { let response = await fetch('api/book/' + id) response = await response.json() bookSet(response) } if (!book) return false //first render, when useEffect did't triggered yet we will return false return <div>{JSON.stringify(book)}</div> }
fuente
También me encontré con el mismo problema una vez y lo solucioné asegurándome de pasar valores primitivos en el segundo argumento
[]
.Si pasa un objeto, React almacenará solo la referencia al objeto y ejecutará el efecto cuando cambie la referencia, que suele ser cada vez (aunque no sé cómo).
La solución es pasar los valores en el objeto. Puedes probar,
const obj = { keyA: 'a', keyB: 'b' } useEffect(() => { // do something }, [Object.values(obj)]);
o
const obj = { keyA: 'a', keyB: 'b' } useEffect(() => { // do something }, [obj.keyA, obj.keyB]);
fuente
Como se dice en la documentación ( https://reactjs.org/docs/hooks-effect.html ), el
useEffect
gancho está destinado a ser utilizado cuando desee que se ejecute algún código después de cada renderizado . De los documentos:Si desea personalizar esto, puede seguir las instrucciones que aparecen más adelante en la misma página ( https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects ). Básicamente, el
useEffect
método acepta un segundo argumento , que React examinará para determinar si el efecto debe activarse nuevamente o no.useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // Only re-run the effect if count changes
Puede pasar cualquier objeto como segundo argumento. Si este objeto permanece sin cambios, su efecto solo se activará después de la primera montura . Si el objeto cambia, el efecto se activará nuevamente.
fuente
Si está creando un gancho personalizado , a veces puede causar un bucle infinito con el valor predeterminado de la siguiente manera
function useMyBadHook(values = {}) { useEffect(()=> { /* This runs every render, if values is undefined */ }, [values] ) }
La solución es usar el mismo objeto en lugar de crear uno nuevo en cada llamada de función:
const defaultValues = {}; function useMyBadHook(values = defaultValues) { useEffect(()=> { /* This runs on first call and when values change */ }, [values] ) }
Si se encuentra con esto en el código de su componente, el bucle puede arreglarse si usa defaultProps en lugar de los valores predeterminados de ES6
function MyComponent({values}) { useEffect(()=> { /* do stuff*/ },[values] ) return null; /* stuff */ } MyComponent.defaultProps = { values = {} }
fuente
No estoy seguro de si esto funcionará para usted, pero podría intentar agregar .length como este:
useEffect(() => { // fetch from server and set as obj }, [obj.length]);
En mi caso (¡estaba obteniendo una matriz!), Obtuvo datos en el montaje, luego nuevamente solo en el cambio y no entró en un bucle.
fuente
Si incluye una matriz vacía al final de useEffect :
useEffect(()=>{ setText(text); },[])
Funcionaría una vez.
Si incluye también el parámetro en la matriz:
useEffect(()=>{ setText(text); },[text])
Se ejecutará siempre que cambie el parámetro de texto.
fuente
Tu bucle infinito se debe a la circularidad
useEffect(() => { setIngredients({}); }, [ingredients]);
setIngredients({});
cambiará el valor deingredients
(devolverá una nueva referencia cada vez), que se ejecutarásetIngredients({})
. Para resolver esto, puede usar cualquiera de los enfoques:const timeToChangeIngrediants = ..... useEffect(() => { setIngredients({}); }, [timeToChangeIngrediants ]);
setIngrediants
se ejecutará cuandotimeToChangeIngrediants
haya cambiado.Object.values(ingrediants)
como segundo argumento a useEffect.useEffect(() => { setIngredients({}); }, Object.values(ingrediants));
fuente
Creo que están tratando de expresar la posibilidad de que uno pueda estar usando datos obsoletos y ser conscientes de esto. No importa el tipo de valores que enviemos
array
para el segundo argumento siempre que sepamos que si alguno de esos valores cambia, se ejecutará el efecto. Si lo estamos utilizandoingredients
como parte del cálculo dentro del efecto, deberíamos incluirlo en elarray
.const [ingredients, setIngredients] = useState({}); // This will be an infinite loop, because by shallow comparison ingredients !== {} useEffect(() => { setIngredients({}); }, [ingredients]); // If we need to update ingredients then we need to manually confirm // that it is actually different by deep comparison. useEffect(() => { if (is(<similar_object>, ingredients) { return; } setIngredients(<similar_object>); }, [ingredients]);
fuente
La mejor manera es comparar el valor anterior con el valor actual usando usePrevious () y _.isEqual () de Lodash . Importe isEqual y useRef . Compare su valor anterior con el valor actual dentro de useEffect () . Si son iguales, no actualice nada más. usePrevious (value) es un gancho personalizado que crea una referencia con useRef () .
A continuación se muestra un fragmento de mi código. Estaba enfrentando un problema de bucle infinito con la actualización de datos usando firebase hook
import React, { useState, useEffect, useRef } from 'react' import 'firebase/database' import { Redirect } from 'react-router-dom' import { isEqual } from 'lodash' import { useUserStatistics } from '../../hooks/firebase-hooks' export function TMDPage({ match, history, location }) { const usePrevious = value => { const ref = useRef() useEffect(() => { ref.current = value }) return ref.current } const userId = match.params ? match.params.id : '' const teamId = location.state ? location.state.teamId : '' const [userStatistics] = useUserStatistics(userId, teamId) const previousUserStatistics = usePrevious(userStatistics) useEffect(() => { if ( !isEqual(userStatistics, previousUserStatistics) ) { doSomething() } })
fuente
En caso de que necesite comparar el objeto y cuando se actualice, aquí hay un
deepCompare
gancho para comparar. La respuesta aceptada seguramente no aborda eso. Tener una[]
matriz es adecuado si necesita que el efecto se ejecute solo una vez cuando esté montado.Además, otras respuestas votadas solo abordan una verificación de tipos primitivos haciendo
obj.value
o algo similar para llegar primero al nivel donde no está anidado. Este puede no ser el mejor caso para objetos profundamente anidados.Así que aquí hay uno que funcionará en todos los casos.
import { DependencyList } from "react"; const useDeepCompare = ( value: DependencyList | undefined ): DependencyList | undefined => { const ref = useRef<DependencyList | undefined>(); if (!isEqual(ref.current, value)) { ref.current = value; } return ref.current; };
Puedes usar lo mismo en
useEffect
ganchoReact.useEffect(() => { setState(state); }, useDeepCompare([state]));
fuente
También puede desestructurar el objeto en la matriz de dependencia, lo que significa que el estado solo se actualizará cuando se actualicen ciertas partes del objeto.
Por el bien de este ejemplo, digamos que los ingredientes contienen zanahorias, podríamos pasar eso a la dependencia, y solo si las zanahorias cambiaran, el estado se actualizaría.
Luego, podría llevar esto más allá y solo actualizar la cantidad de zanahorias en ciertos puntos, controlando así cuándo se actualizaría el estado y evitando un bucle infinito.
useEffect(() => { setIngredients({}); }, [ingredients.carrots]);
Un ejemplo de cuándo podría usarse algo como esto es cuando un usuario inicia sesión en un sitio web. Cuando inician sesión, podemos desestructurar el objeto de usuario para extraer su función de cookies y permisos, y actualizar el estado de la aplicación en consecuencia.
fuente
mi caso fue especial al encontrar un bucle infinito, el senario era así:
Tenía un objeto, digamos objX que proviene de accesorios y lo estaba desestructurando en accesorios como:
const { something: { somePropery } } = ObjX
y utilicé el
somePropery
como una dependencia a miuseEffect
gusto:useEffect(() => { // ... }, [somePropery])
y me causó un bucle infinito, traté de manejar esto pasando el conjunto
something
como una dependencia y funcionó correctamente.fuente