Bucle infinito en usoEfecto
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?
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/
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>
}
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]);