3
votes

I'm currently creating a to-do list within React which retrieves tasks from Firestore and stores them locally within an array using state hooks: const [todoList, setTodoList] = useState([]). I've run into some roadblocks while coding this mainly because Firestore's onSnapshot function doesn't seem to play properly with React. The snapshot code is called on load (to retrieve existing tasks) and when a new task is created. The snapshot code for appending changes to an array is:

todoReference.onSnapshot(colSnapshot => {
    colSnapshot.docChanges().forEach(change => {
        if (change.type === 'added') {
            const taskData = change.doc.data();

            todoList.push(taskData);
        }
    });
    setTodoList(todoList); // "update state" using new array
});

There are a few issues which pop-up when I try different variations of this (pushing to empty array and then concatenating the two arrays together, etc.):

  • The todo list state doesn't persist on new snapshot. For example, creating a new task task2 updates the todoList to [task2], but creating another task task3 after that makes the first task disappear and updates the array to [task3] instead of [task2, task3].

  • onSnapshot keeps retrieving the same tasks despite them being previously retrieved. For example, on load the initial todoList is updated to [task1, task2, task3]. When creating a new task and calling the snapshot function again, I expect the todoList to be updated to [task1, task2, task3, task4]. However, instead I'm returned some variation of [task1, task2, task3, task1, task2, task3, task4] which compounds whenever the snapshot function is called again.

This issue seems to only happen in React and not native JavaScript (the tasks are created and retrieved just fine without any duplicates). Some solutions I have tried is wrapping everything within a onEffect (which I believe gave me the first problem if I didn't pass todoList as a dependency, and if I did would infinitely loop) and calling the snapshot function via unsubscribe() (which still gave me the second problem).

1

1 Answers

2
votes

Solved! I nested everything within a useEffect with no dependencies and managed to resolve the first bullet-point regarding states not updating properly. Instead of setting state normally using setTodoList(todoList.concat(newTasks)), I functionally set the state using setTodoList(currentList => currentList.concat(newTasks)) (something about setState being asynchronous about useState, async isn't my specicalty). Find the answer here: https://stackoverflow.com/a/56655187/9477642.

Here's the final snapshot update code (I somehow also resolved onSnapshot returning the entire collection instead of just changes each time but I forgot how, I'll update this if I remember why):

useEffect(() => {
    let unsubscribe = todoReference.onSnapshot(colSnapshot => {
        console.log(colSnapshot.docChanges());
        let newTasks = [];
        colSnapshot.docChanges().forEach(change => {
            if (change.type === 'added') {
                const taskData = change.doc.data();

                newTasks.push(taskData);
            }
        });
        setTodoList(currentList => currentList.concat(newTasks));
    });
    return () => unsubscribe();
}, []);