我正在尝试在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中获取了所有文档之后,仅保留了最后一个文档中的数据。
答案 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);
}
在我的用例中,我不得不忽略本地更改,因此与本地更改和监听元数据更改相关的修改与我最初的问题无关。