Search code examples
reactjsfirebasereact-nativechataddeventlistener

React Native Firebase Chat app - Add Listener for new incoming messages


I'm creating a chat feature in my application that leverages Firebase. I've read the docs about adding event listeners, but I can't seem to get the listener to update the state (I believe when the state gets updated, the app re-renders and should have the new, incoming message)

Use Case: you're having a chat with someone and you have the messaging/chat screen up and dont want to have to refresh the page to see new messages... they should display as the message gets posted to the DB

 componentDidMount() {
        this.setState({ isLoading: true })
        this.pullMessages()
        this.listener()
        this.setState({ isLoading: false })
   pullMessages = () => {
        let messagesArray = []
        if (this.state.chatroomDetails.users.length === 2) {
            firebase.firestore().collection('chatrooms').doc('private').collection(this.state.chatroomDetails.key).doc('messages').collection('messages').get().then((querySnapshot) => {
                if (!querySnapshot.empty) {
                    querySnapshot.forEach((doc) => {
                        messagesArray.push(doc.data())
                        this.setState({ messages: messagesArray })
                    })
                }
            })


        }
        else {
            firebase.firestore().collection('chatrooms').doc('group').collection(this.state.chatroomDetails.key).doc('messages').collection('messages').get().then((querySnapshot) => {
                if (!querySnapshot.empty) {
                    querySnapshot.forEach((doc) => {
                        messagesArray.push(doc.data())
                        // console.log(messagesArray)
                        this.setState({ messages: messagesArray })
                    })
                } else {
                    console.log('Nothing to Grab')
                }
            })
        }
    }
 listener = () => {
        let messagesArray = []
        if (this.state.chatroomDetails.users.length === 2) {
            firebase.firestore().collection('chatrooms').doc('private').collection(this.state.chatroomDetails.key).doc('messages').collection('messages')
                .onSnapshot(function (querySnapshot) {
                    if (!querySnapshot.empty) {
                        querySnapshot.forEach(function (doc) {
                            console.log('the console');
                            console.log(doc.data());
                            messagesArray.push(doc.data())
                        })
                    }
                })
            this.setState({ messages: messagesArray })
        }
    }

in listener(), i can see the new message that comes through from the other device, but i can't seem to set it to my state from here... help!


Solution

  • The state is likely not updating because you are mutating it. When this line runs:

    messagesArray.push(doc.data())
    this.setState({ messages: messagesArray })
    

    You are adding an item to the messages array that is already in the state and then setting the state to the same value which won't re-render your component.

    You can correct this by setting the messages value to a new array each time you receive an update on the collection. Like this:

    this.setState({ messages: [...messagesArray, doc.data()] })
    

    EDIT:

    I have also noticed that your state update is not in the snapshot listener here:

                    .onSnapshot((querySnapshot) => {
                        if (!querySnapshot.empty) {
                            querySnapshot.forEach((doc) => {
                                console.log('the console');
                                console.log(doc.data());
                                messagesArray.push(doc.data())
                            })
                        }
                    })
                this.setState({ messages: messagesArray })
    

    It should look this like:

                    .onSnapshot((querySnapshot) => {
                        if (!querySnapshot.empty) {
                            querySnapshot.forEach((doc) => {
                                this.setState({ messages: [...messagesArray, doc.data() })
                            })
                        }
                    })
                
    

    or even better to prevent needlessly updating the state:

                    .onSnapshot((querySnapshot) => {
                        if (!querySnapshot.empty) {
                            const newMessages = querySnapshot.map((doc) => doc.data())
    
                            this.setState({ messages: [...messagesArray, ...newMessages })
                        }
                    })