我正在尝试在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 的组件中设置了日志。当它是一个空数组时,它会被记录下来,但在它被更新后不会被记录,这意味着状态更新不会在 props 更新中传播。
我还在该帮助程序函数中看到了一些奇怪的行为。"旧状态"日志永远不会像第一次调用时那样记录空数组。我从来没有在没有该函数的情况下设置状态,因此当状态从空数组的初始值更改为其他值时,应该进行调用。相反,我得到的第一个旧状态日志是从数据库中获取的数据。不过,所有其他日志看起来都没问题。
知道这里出了什么问题吗?提前感谢!
更新:
studentSubscribe
将const _students = students
更改为const _students = [...students]
部分解决了问题,因为现在状态变量不会搞砸。
现在,似乎在students
数组中,从 firestore 获取所有文档后,只保留了最后一个获取的文档中的数据。
所以问题是初始获取必须在另一个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);
}
对于我的用例,我不得不忽略本地更改,因此与本地更改相关的修改和侦听元数据更改与我的初始问题无关。