在反应异步中更新状态行为不正确

时间:2020-01-02 10:48:06

标签: javascript reactjs google-cloud-firestore react-hooks

我正在尝试在useEffect()钩子订阅中设置一系列Firebase文档,如下所示:

useEffect(() => {
            const db = firestore();
            const unsubscribeCallbacks: (() => void)[] = [];
            db.collection(CLASS_COLLECTION).doc(id).get().then(doc => {
                const classData = doc.data() as Class;
                for (let student of classData.students) {
                    const unsubscribe = student.onSnapshot(studentSubscribe);
                    unsubscribeCallbacks.push(unsubscribe)
                }
            })
            return () => {
                for (let unsubscribe of unsubscribeCallbacks) {
                    unsubscribe();
                }
            }
        }, [id]
    );

studentSubscribe是处理从数据库获取的数据并更新状态的函数:

function studentSubscribe(snapshot: DocumentSnapshot) {
        const _students = students;
        const studentData = snapshot.data() as Student;
        //Check if this snapshot is a student update or a new student
        //Also true on the first fetch
        const isStudent = _students.find(val => val.id === studentData.id)
        if (isStudent) {
            console.log("Student updated")
            console.log(studentData)
            _students.map(val => val.id === studentData.id ? studentData : val)
            //Sort alphabetically
            _students.sort((a, b) => {
                if (a.name < b.name)
                    return -1;
                else if (a.name === b.name)
                    return 0;
                return 1;
            });
        } else {
            console.log("New student pushed to state")
            console.log(studentData)
            _students.push(studentData);
            //Sort alphabetically
            _students.sort((a, b) => {
                if (a.name < b.name)
                    return -1;
                else if (a.name === b.name)
                    return 0;
                return 1;
            });
        }
        console.log("Pushing to state");
        console.log(_students);
        updateStudents(_students);
    }

updateStudents只是一个辅助函数,因此我可以添加一些额外的日志记录:

function updateStudents(newState:Student[]){
    console.log('Old state')
    console.log(students)
    console.log('New state');
    console.log(newState);
    setStudents(newState);
}

将学生状态初始化为空数组。问题是从数据库中获取数据并更新状态后,状态更改不会反映在重新渲染中。我在将student状态作为prop的组件中设置了日志。当它是一个空数组时,它会被记录下来,但是不会在更新后记录下来,这意味着状态更新不会在道具更新中传播。

我还看到了该助手功能中的一些奇怪行为。 “旧状态”日志永远不会像在第一次调用时那样记录空数组。我从来没有设置没有该函数的状态,因此当状态从空数组的初始值更改为其他值时,应该进行调用。相反,我得到的第一个旧状态日志是从数据库中获取的数据。其他所有日志看起来都没问题。

你知道这里出了什么问题吗?预先感谢!

更新

studentSubscribe中,将const _students = students更改为const _students = [...students]部分地解决了该问题,因为现在状态变量不再混乱了。

现在,似乎在students数组中,从firestore中获取了所有文档之后,仅保留了最后一个文档中的数据。

1 个答案:

答案 0 :(得分:0)

所以问题在于,初始取回必须在另一个useEffect中进行,否则文档将相互覆盖,因此仅最后取回的文档将保持该状态。所以我将代码更改为此

//State to mark that all students have been fetched from firestore on mount
const [initialFetchDone, setInitialFetchDone] = useState(false);

 useEffect(() => {
        const db = firestore();
        db.collection(CLASS_COLLECTION).doc(id).get().then(snapshot => {
            const fetchedClass = snapshot.data() as Class;
            setClass(fetchedClass);
            const docs = fetchedClass.students.map(doc => doc.get());
            Promise.all(docs).then(docs => docs.map(doc => doc.data())).then(_students => setStudents(_students as Student[])
            ).then(() => setInitialFetchDone(true))
        })
    }, [id]);

    useEffect(() => {
            const db = firestore();
            const unsubscribeCallbacks: (() => void)[] = [];
            //The initial fetched is handled in the other effect
            if (initialFetchDone) {
                db.collection(CLASS_COLLECTION).doc(id).get().then(doc => {
                    const classData = doc.data() as Class;
                    for (let student of classData.students) {
                        const unsubscribe = student.onSnapshot({includeMetadataChanges:true},studentSubscribe);
                        unsubscribeCallbacks.push(unsubscribe)
                    }
                })
                return () => {
                    for (let unsubscribe of unsubscribeCallbacks) {
                        unsubscribe();
                    }
                }
            }
        }, [id, _class, initialFetchDone]
    );


    function studentSubscribe(snapshot: DocumentSnapshot) {
        const _students = [...students];
        //Ignore local changes
        if (!snapshot.metadata.hasPendingWrites) {
            const studentData = snapshot.data() as Student;
            //Check if this snapshot is a student update or a new student
            //Also true on the first fetch
            const isStudent = _students.find(val => val.id === studentData.id)
            if (isStudent) {
                _students.map(val => val.id === studentData.id ? studentData : val)
                //Sort alphabetically
                _students.sort((a, b) => {
                    if (a.name < b.name)
                        return -1;
                    else if (a.name === b.name)
                        return 0;
                    return 1;
                });
            } else {

                _students.push(studentData);
                //Sort alphabetically
                _students.sort((a, b) => {
                    if (a.name < b.name)
                        return -1;
                    else if (a.name === b.name)
                        return 0;
                    return 1;
                });
            }
        }
        setStudents(_students);
    }

在我的用例中,我不得不忽略本地更改,因此与本地更改和监听元数据更改相关的修改与我最初的问题无关。