Javascript 在react async中更新状态行为不正确

Javascript 在react async中更新状态行为不正确,javascript,reactjs,google-cloud-firestore,react-hooks,Javascript,Reactjs,Google Cloud Firestore,React Hooks,我试图在useffect()hook中设置对firebase文档数组的订阅,如下所示: useEffect(() => { const db = firestore(); const unsubscribeCallbacks: (() => void)[] = []; db.collection(CLASS_COLLECTION).doc(id).get().then(doc => {

我试图在
useffect()
hook中设置对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);
    }
学生状态用空数组初始化。问题在于,从数据库获取数据并更新状态后,状态更改不会反映在重新渲染器中。我在接收
student
状态作为道具的组件中设置了日志。当它是一个空数组时,它会被记录下来,但在它被更新后不会被记录,这意味着状态更新不会在道具更新中传播

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

知道这里出了什么问题吗?提前谢谢

更新

studentSubscribe
中,将
const\u students=students
更改为
const\u students=[…students]
部分修复了该问题,因为现在状态变量不会出错


现在,似乎在
students
数组中,从firestore获取所有文档后,只保留上次提取的文档中的数据。

因此,问题是初始提取必须在另一个
useffect
中完成,否则文档将相互覆盖,因此只有上次提取的文档将保持状态。所以我把代码改成了这个

//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);
    }
//声明已从挂载上的firestore获取所有学生
const[initialFetchDone,setInitialFetchDone]=useState(false);
useffect(()=>{
const db=firestore();
db.collection(CLASS_collection).doc(id).get()。然后(快照=>{
const fetchedClass=snapshot.data()作为类;
setClass(fetchedClass);
const docs=fetchedClass.students.map(doc=>doc.get());
Promise.all(docs).then(docs=>docs.map(doc=>doc.data()).then(_students=>setStudents(_studentsas Student[]))
).然后(()=>setInitialFetchDone(true))
})
},[id]);
useffect(()=>{
const db=firestore();
const unsubscribe回调:(()=>void)[]=[];
//在另一个效果中处理最初获取的数据
如果(完成){
db.collection(CLASS_collection).doc(id).get().then(doc=>{
const classData=doc.data()作为类;
for(让学生使用classData.students){
const unsubscribe=student.onSnapshot({includeMetadataChanges:true},studentSubscribe);
取消订阅回调。推送(取消订阅)
}
})
return()=>{
for(让我们取消订阅取消订阅回调){
取消订阅();
}
}
}
},[id,_类,initialFetchDone]
);
函数studentSubscribe(快照:DocumentSnapshot){
const_students=[…students];
//忽略本地更改
如果(!snapshot.metadata.hasPendingWrites){
const studentData=snapshot.data()作为学生;
//检查此快照是学生更新还是新学生
//在第一次提取时也是如此
const isStudent=\u students.find(val=>val.id==studentData.id)
如果(是学生){
_students.map(val=>val.id==studentData.id?studentData:val)
//按字母顺序排序
_学生。排序((a,b)=>{
如果(a.name{
如果(a.name

对于我的用例,我必须忽略本地更改,因此与修改相关的本地更改和侦听元数据更改与我的初始问题无关。

useEffect必须取决于studentState,如[id,students,setStudents]此useEffect修改
students
状态,因此,作为依赖项传入
students
,不仅会导致无限循环?只有当“id”发生更改时,才能获取数据。id发生更改时,这是一个状态属性,我需要更改我订阅的文档是的,发现了问题。在本地变量
\u students
中复制
students
时,我直接分配数组,而不是使用
[…students]
复制数组。现在我有了另一个
students
数组,它包含了刚刚从firestore检索到的最后一个文档中的数据。让我更新一下帖子
//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);
    }