Search code examples
reactjsfirebasereact-hooksuse-effectuse-state

Only first element is showing state updates until external event


Related and an almost direct extension of my earlier question

I am using useEffect to run a function that grabs data from firebase. The data is grabbed, however only the first element appears to show the additional information added to the state.

const [expenses, setExpenses] = useState([]);
const roster = [];
var db = firebase.firestore();

React.useEffect(() => {
    const getRoster = async () => {
      db.collection("Roster")
        .get()
        .then((querySnapshot) => {
          querySnapshot.forEach((doc) => {
            // doc.data() is never undefined for query doc snapshots
            var data = doc.data();
            data.ID = doc.id;

            roster.push(data);
          });

          // setExpenses(roster);
          // console.log("expenses", expenses);
          // console.log("roster", roster);
        })



This is working as expected, the commented out code was the solution to my previous question. I added in the code below and experienced my new issue.

        .then(() => {
          roster.forEach((member) => {
            let total = 0;
            db.collection("Attendance Entries")
              .where("attendees", "array-contains", member.ID)
              .get()
              .then((querySnapshot) => {
                querySnapshot.forEach((doc) => {
                  // doc.data() is never undefined for query doc snapshots
                  total++;
                  member.total = total;
                });
                setExpenses(roster);
              });
          });
        });
    };

    getRoster();
  }, []);


The first element shows the "member.total" on the inital load, the others only appear after the state is changed. Not sure why that is the case..

Thank you for the help!

Example of what I am seeing on initial load


Solution

  • I don't quite follow all of your code, but I see a possible issue with the way you enqueue state updates in the forEach loop in the section of code you say you've an issue with. When you enqueue state updates in a loop you must use a functional state update so each enqueued update doesn't blow away (overwrite) the previous state.

    You can iterate the roster array you've previously computed and then compute the docs total for each member and mutate the member object, then use a functional state update to append the new member object to the expenses state array.

    .then(() => {
      roster.forEach((member, index) => {
        let total = 0;
        db.collection("Attendance Entries")
          .where("attendees", "array-contains", member.ID)
          .get()
          .then((querySnapshot) => {
            querySnapshot.forEach((doc) => {
              // doc.data() is never undefined for query doc snapshots
              total++;
            });
            member.total = total;
            setExpenses(expenses => expenses.concat(member));
          });
      });
    });