Martin Pichler
Martin Pichler

Reputation: 315

React useState and Firebase onSnapshot

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.

Upvotes: 3

Views: 8274

Answers (4)

webbyweb
webbyweb

Reputation: 435

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

Upvotes: 0

Martin Pichler
Martin Pichler

Reputation: 315

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]);

Upvotes: 11

Frank van Puffelen
Frank van Puffelen

Reputation: 599996

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.

Upvotes: 1

Arshal Daison
Arshal Daison

Reputation: 126

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

Upvotes: 1

Related Questions