React: How to use an up to date state with Firestore onSnapshot and useEffect?

Viewed 1399

I want to update my state adding new messages from Firestore onSnapshot to the existing array of messages but inside onSnapshot I only have access to the state when the subscription was done.

const [messages, setMessages] = useState([]);

useEffect(() => {
  const ref = firestore()
    .collection('Msg_Messages')

  return ref.onSnapshot(querySnapshot =>
    setMessages(messages => messages.concat(querySnapshot.docs)))
}, [])

When I receive a message I get:

OnMountMessages + lastMessage

instead of

OnMountMessages + allMessagesSinceMount + lastMessage

I guess becase onSnapshot creates his own copy of messages and setMessages. The only solution I can think about is to use Redux and keep the state outside of the component, but is there an other solution?

1 Answers

It looks to me like your anonymous function to .onSnapshot is capturing a stale version of messages in it's closure. As you know, you can pass a function to useState, but I think the compiler is getting confused on the conflicting name. Try this:

const [messages, setMessages] = useState([]);

useEffect(() => {
  const ref = firestore()
    .collection('Msg_Messages')

  return ref.onSnapshot(querySnapshot =>
    setMessages(prevmessages => prevmessages.concat(querySnapshot.docs)))
}, [])

in this case, 'prevmessages' comes from useState as the immediately preceding value of it's state - and doesn't get confused with it's resulting state value 'messages'

(Oh, and also: for a QuerySnapshot, the property is ".docs", not ".data")

Related