es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿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}}

Tags:  , , ,

Answer

  1. Avatar for davy.ai

    El problema aquí es con el comportamiento asíncrono de la función useState(). Cuando llama la función setCounter() con un nuevo valor, no actualiza el valor de counter inmediatamente. En su lugar, programa una actualización al estado de counter, que puede o no ocurrir inmediatamente.

    En su caso, está llamando la función setCounter() dentro de una función map(), que puede ejecutarse varias veces según el número de documentos en la instantánea de Firebase. Ahora, cuando llama la función setCounter() dentro de la función map(), puede programar múltiples actualizaciones al estado de counter según la condición. Tenga en cuenta que cada vez que llama a setCounter(), no actualiza el estado inmediatamente, sino que programa una actualización.

    Dado que console.log({ counter }); se llama fuera de la función map(), no se garantiza que todas las actualizaciones al estado de counter se hayan completado en el momento en que se ejecuta. Por lo tanto, puede registrar el estado antiguo de counter, que es {mona: 0, phil: 0}, o un estado parcialmente actualizado de counter, 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 de counter y devuelva el nuevo estado según la condición. Esto asegura que todas las actualizaciones al estado de counter se apliquen en secuencia, y console.log({ counter }); registra el último estado de counter. Así es como puede modificar su código:

    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"); // Se ejecuta como se espera (por ejemplo, 3 veces)
              setCounter((prevCounter) => ({ ...prevCounter, mona: prevCounter.mona + 1 }));
            }
            if (doc.data().wer === "Phil") {
              console.log("Phil + 1"); // Se ejecuta como se espera (por ejemplo, 6 veces)
              setCounter((prevCounter) => ({ ...prevCounter, phil: prevCounter.phil + 1 }));
            }
            return {
              ...doc.data(),
              id: doc.id,
              timestamp: doc.data().timestamp?.toDate().getTime(),
            };
          })
        );
        setLoading(false);
      });
    }, []);
    
    useEffect(() => {
      console.log({ counter }); // Se ejecuta para cada actualización del estado de `counter`.
    }, [counter]);
    

    Ahora, cuando llama la función setCounter(), pasa una función que toma el estado anterior de counter y devuelve el nuevo estado según la condición. Esto asegura que todas las actualizaciones al estado de counter se apliquen en secuencia, y console.log({ counter }); registra el último estado de counter.

Comments are closed.