This is my current code:
useEffect(() => {
profile.familyCode.forEach((code) => {
console.log(code._id)
onSnapshot(query(collection(db, "group-posts", code._id, "posts"), orderBy("timestamp", "desc")
),
(querySnapshot) => {
const posts = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setMessages([...messages, posts])
}
)
})
There are TWO code._id's and currently it is only setting my messages from one of them. What am I missing here?
Ive tried using some of firestores logical expressions to do the same thing with no success. This way I can at least pull some of them, but I would like to pull ALL of the posts from BOTH code._id's
CodePudding user response:
You are missing the fact that setMessages
is not updating messages
itself immediately. So messages
are closure-captured here with the old (or initial value) and calling setMessages
will just overwrite what was previously set by previous onSnapshot
.
Next issue - onSnapshot
returns the unsubscribe
function which should be called to stop the listener. Or you will get some bugs and memory leaks.
Here is a fast-written (and not really tested) example of possible solution, custom hook
.
export function useProfileFamilyGroupPosts(profile) {
const [codeIds, setCodeIds] = useState([]);
const [messagesMap, setMessagesMap] = useState(new Map());
const messages = useMemo(() => {
if (!messagesMap || messagesMap.size === 0) return [];
// Note: might need some tweaks/fixes. Apply .flatMap if needed.
return Array.from(messagesMap).map(([k, v]) => v);
}, [messagesMap])
// extract codeIds only, some kind of optimization
useEffect(() => {
if (!profile?.familyCode) {
setCodeIds([]);
return;
}
const codes = profile.familyCode.map(x => x._id);
setCodeIds(curr => {
// primitive arrays comparison, replace if needed.
// if curr is same as codes array - return curr to prevent any future dependent useEffects executions
return curr.sort().toString() === codes.sort().toString() ? curr : codes;
})
}, [profile])
useEffect(() => {
if (!codeIds || codeIds.length === 0) {
setMessagesMap(new Map());
return;
}
const queries = codeIds.map(x => query(collection(db, "group-posts", x, "posts"), orderBy("timestamp", "desc")));
const unsubscribeFns = queries.map(x => {
return onSnapshot(x, (querySnapshot) => {
const posts = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
// update and re-set the Map object.
setMessagesMap(curr => {
curr.set(x, posts);
return new Map(curr)
})
});
});
// we need to unsubscribe to prevent memory leaks, etc
return () => {
unsubscribeFns.forEach(x => x());
// not sure if really needed
setMessagesMap(new Map());
}
}, [codeIds]);
return messages;
}
The idea is to have a Map
(or just {}
key-value object) to store data from snapshot listeners and then to flat that key-value
to the resulting messages
array. And to return those messages from hook.
Usage will be
const messages = useProfileFamilyGroupPosts(profile);