2

I want to automatically fetch new incoming messages from a firestore collection using onSnapshot. While I can set the state inside the callback, I cannot read it.

const [messages, setMessages] = useState(null);
const [chat, setChat] = useState(props.chatId);

useEffect(() => {
        const q = query(collection(db, "messages"), where("chat_id", "==", chat), orderBy("date","desc"), limit(5));
        // Create the DB listener
        const unsuscribe = onSnapshot(q, (querySnapshot) => {
            console.log(messages);
            if(messages === null){
                console.log("setting messages the first time");
                setMessages(querySnapshot.docs)
            }else{
                console.log("updating messages");
                setMessages([...querySnapshot.docs, ...messages])
            }
        });
        return () => {
            console.log("unsubscribe");
            unsuscribe();
        }
    }, [chat]);

Whenever onSnapshot fires, messages is always null but setMessages works since the messages are displayed. I tried so many approaches but I could not get it to work.

Help much appreciated.

Martin Pichler
  • 185
  • 2
  • 10

4 Answers4

9

So I managed to find a solution. The trick is to listen for state changes of messages with useEffect()

const [snapShot, setSnapshot] = useState(null);
const [messages, setMessages] = useState(null);
const [chat, setChat] = useState(props.chatId);

useEffect(() => {
        const q = query(collection(db, "messages"), where("chat_id", "==", chat), orderBy("date","desc"), limit(5));
       
        const unsuscribe = onSnapshot(q, (querySnapshot) => {
            setSnapShot(querySnapshot)
        });
        return () => {
            unsuscribe();
        }
    }, [chat]);

useEffect(() => {
  if(messages === null){
                console.log("setting messages the first time");
                setMessages(snapShot.docs)
            }else{
                console.log("updating messages");
                setMessages([...snapShot.docs, ...messages])
            }
    }, [snapShot]);
Martin Pichler
  • 185
  • 2
  • 10
  • Thanks for the tweak. This is weird, the callback can access the state messages value, but somehow the state value stays the same which is null. Even if you setMessages with new value. Maybe it's a bug! What kind of bug is that anyway? scope? – Anh Nguyen May 21 '22 at 13:51
1

did you try this from the documentation

const q = query(collection(db, "cities"), where("state", "==", "CA"));
const unsubscribe = onSnapshot(q, (snapshot) => {
  snapshot.docChanges().forEach((change) => {
    if (change.type === "added") {
        console.log("New city: ", change.doc.data());
    }
    if (change.type === "modified") {
        console.log("Modified city: ", change.doc.data());
    }
    if (change.type === "removed") {
        console.log("Removed city: ", change.doc.data());
    }
  });
});

Also the question is a bit confusing on what you want to achieve

Arshal_d
  • 116
  • 1
  • 6
  • 1
    That's what I am doing. The problem is, that inside the onSnapshot callback I cannot read the current state of messages. I simply want to listen for changes in the collection and add them to the already existing ones. – Martin Pichler Dec 03 '21 at 15:06
1

The querySnapshot you get from Firestore always contains all snapshots that match the query, not just the changed/new documents.

So your onSnapshot handler can be much simpler:

const unsubscribe = onSnapshot(q, (querySnapshot) => {
    setMessages(querySnapshot.docs)
});

So: every time you get notified by Firestore that the data in q has changed, you pass the data on to the state/UI for rendering.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thats not true for my query because it has limit(5). It works like this. I load the last 5 messages, then when there is a change I add the new ones to the state. But the weird thing is that setMessages works but messages doesn't. – Martin Pichler Dec 03 '21 at 16:26
  • Ah, I missed the limit. But you're not adding only the changed documents, you're adding all 5. To properly handle only changes, iterate over `querySnapshot.docChanges` as shown here: https://firebase.google.com/docs/firestore/query-data/listen#view_changes_between_snapshots – Frank van Puffelen Dec 03 '21 at 17:20
0

It shows up as null because when you create the snapshot callback it's using values that belong to that render. Similar to if you tried to access useState values in an event callback.

See here: https://stackoverflow.com/a/55265764/8846296

A solution is to use useRef to store your value instead of useState. And then you can access it in your callback using myValue.current

webbyweb
  • 385
  • 2
  • 11