Bucle infinito en usoEfecto

Resuelto WhiteFluffy asked hace 5 años • 17 respuestas

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 configurarlo nuevamente como 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, lo que hace que React piense que sigue cambiando.

useEffect(() => {
  setIngredients({});
}, [ingredients]);

Lo mismo ocurre con las matrices, pero como primitivo no se atascará 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?

WhiteFluffy avatar Oct 31 '18 01:10 WhiteFluffy
Aceptado

Pasar una matriz vacía como segundo argumento para useEffect hace que solo se ejecute al montar y desmontar, deteniendo así cualquier bucle infinito.

useEffect(() => {
  setIngredients({});
}, []);

Esto me lo aclararon en la publicación del blog sobre React Hooks en https://www.robinwieruch.de/react-hooks/

WhiteFluffy avatar Oct 30 '2018 23:10 WhiteFluffy

Tuve 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 cada componente/representación principal, debe usar:

  useEffect(() => {

    // don't know where it can be used :/
  })

Para ejecutar cualquier cosa solo una vez después del montaje del componente (se representará 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 cualquier cosa una vez en el montaje del componente 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) 

  const loadBookFromServer = useCallback(async () => {
    let response = await fetch('api/book/' + id)
    response  = await response.json() 
    bookSet(response)
  }, [id]) // every time id changed, new book will be loaded

  useEffect(() => {
    loadBookFromServer()
  }, [loadBookFromServer]) // useEffect will run once and when id changes


  if (!book) return false //first render, when useEffect did't triggered yet we will return false

  return <div>{JSON.stringify(book)}</div>  
}
ZiiMakc avatar Oct 31 '2018 20:10 ZiiMakc

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 la referencia cambie, lo que generalmente ocurre cada vez (aunque ahora 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
}, [obj.keyA, obj.keyB]);
Dinesh Pandiyan avatar Feb 09 '2019 04:02 Dinesh Pandiyan