¿Por qué useState dentro de useEffect no está funcionando en React?
Uso useEffect()
para obtener una instantánea de Firestore y al mismo tiempo quiero contar un valor:
const [counter, setCounter] = useState({ mona: 0, phil: 0 });
useEffect(() => {
onSnapshot(q, (snapshop) => {
setTasks(
snapshop.docs.map((doc) => {
if (doc.data().wer === "Mona") {
console.log("Mona + 1"); // Esto se ejecuta como se espera (por ejemplo, 3 veces)
setCounter({ ...counter, mona: counter.mona + 1 });
}
if (doc.data().wer === "Phil") {
console.log("Phil + 1"); // Esto se ejecuta como se espera (por ejemplo, 6 veces)
setCounter({ ...counter, phil: counter.phil + 1 });
}
return {
...doc.data(),
id: doc.id,
timestamp: doc.data().timestamp?.toDate().getTime(),
};
})
);
setLoading(false);
});
}, []);
useEffect(() => {
console.log({ counter }); // esto se ejecuta solo 2 veces.
}, [counter]);
¿Por qué setCounter
no se ejecuta o actualiza correctamente el contador cuando el console.log()
dentro del map()
se ejecuta correctamente?
Por cierto, el console.log({ counter });
no devuelve nada más que:
{counter: {mona: 0, phil: 0}}
{counter: {mona: 0, phil: 1}}
davy.ai
El problema aquí es con el comportamiento asíncrono de la función
useState()
. Cuando llama la funciónsetCounter()
con un nuevo valor, no actualiza el valor decounter
inmediatamente. En su lugar, programa una actualización al estado decounter
, que puede o no ocurrir inmediatamente.En su caso, está llamando la función
setCounter()
dentro de una funciónmap()
, que puede ejecutarse varias veces según el número de documentos en la instantánea de Firebase. Ahora, cuando llama la funciónsetCounter()
dentro de la funciónmap()
, puede programar múltiples actualizaciones al estado decounter
según la condición. Tenga en cuenta que cada vez que llama asetCounter()
, no actualiza el estado inmediatamente, sino que programa una actualización.Dado que
console.log({ counter });
se llama fuera de la funciónmap()
, no se garantiza que todas las actualizaciones al estado decounter
se hayan completado en el momento en que se ejecuta. Por lo tanto, puede registrar el estado antiguo decounter
, que es {mona: 0, phil: 0}, o un estado parcialmente actualizado decounter
, que es {mona: 0, phil: 1}, según cuándo se execute.Para solucionar este problema, puede llamar a la función
setCounter()
con una función que tome el estado anterior decounter
y devuelva el nuevo estado según la condición. Esto asegura que todas las actualizaciones al estado decounter
se apliquen en secuencia, yconsole.log({ counter });
registra el último estado decounter
. Así es como puede modificar su código:Ahora, cuando llama la función
setCounter()
, pasa una función que toma el estado anterior decounter
y devuelve el nuevo estado según la condición. Esto asegura que todas las actualizaciones al estado decounter
se apliquen en secuencia, yconsole.log({ counter });
registra el último estado decounter
.