Search code examples
javascriptfirebasegoogle-cloud-firestore

How do I get data from a firestore collection within a document that requires an argument?


I am trying to create an admin chat where the admin can view all the user messages and then reply to them. I've been able to add documents to this collection but I am unable to display the messages so that the admin can read them all on the page. So far I've tried 2 methods one of which I had used before for similar requests, below.

import { projectFirestore } from "../Firebase/Config";
import { ref, watchEffect } from "vue"

const getChatMess = (id) => {
    const ChatMess = ref([])
    const error = ref(null)
    let lastDoc = null; 

    const load = async () => {
        try {
            let query = projectFirestore.collection('ChatMess').doc(id).collection('Messages').orderBy('createdAt')
            if (lastDoc) {
                query = query.startAfter(lastDoc)
            }

            const res = await query.get()

            if (!res.empty) {
                lastDoc = res.docs[res.docs.length - 1]

                // Add new results to exsiting ones

                ChatMess.value = [...ChatMess.value, ...res.docs.map(doc => {
                  //  console.log(doc.data())
                    return {...doc.data(), id: doc.id}
                })]
            }
        }
        catch (err){
            error.value = err.message
           // console.log(error.value)
        }
    }

    return { ChatMess, error, load }
}

export default getChatMess

And another I made after reading through the firebase docs, when I concole.log(ChatMess) I am able to see an array in the console log but it will not display the values since they are in the array.

import { projectFirestore } from "../Firebase/Config";
import { ref, watchEffect } from "vue"

const getChatMess = (id) => {
    const ChatMess = ref(null)
    const error = ref(null)


            let collectionRef = projectFirestore.collection('Subscribed').doc(id).collection('messages').orderBy('createdAt')
            const unsub = collectionRef.onSnapshot(snap => {
                console.log(snap)
                let results = []
                snap.docs.forEach(doc => {
                  // must wait for the server to create the timestamp & send it back
                  // we don't want to edit data until it has done this
                  doc.data().createdAt && results.push({...doc.data(), id: doc.id})
                });
                
                // update values
                ChatMess.value = results
                error.value = null 
                console.log(ChatMess)
              }, err => {
              
                console.log(err.message)
                ChatMess.value = null
                error.value = 'could not fetch the data'
              })
            
              watchEffect((onInvalidate) => {
                // unsub from prev collection when watcher is stopped (component unmounted)
                onInvalidate(() => unsub());
              });

    return { ChatMess, error }
}

export default getChatMess

Both of these are in their own Javascript files which I then import the getChatMess to the page where I want the messages to be displayed (this is shown below).

<script>
import { projectFirestore } from '../Firebase/Config'
import getChatMess from "../Composables/getChatMess";
import { ref } from 'vue'
import { timestamp } from '../Firebase/Config'
import { useRoute } from 'vue-router'
export default {


  setup(){
    const route = useRoute()
    console.log(route)
    console.log(route.params)
    const { Chatmess, error } = getChatMess(route.params.id);
 
    const message = ref('')
    const Activate = ref(false)
    const handleSubmit = async () => {  
    const NewChatmess = {
        message: message.value,
        createdAt: timestamp(),
    }
    const res = await projectFirestore.collection('Subscribed').doc(route.params.id).collection('messages').add(NewChatmess)
  }
  return { handleSubmit, Activate, message, Chatmess }
}
}
    </script>

    <template>
      <div class="chat-window">
        <div v-for =" doc in Chatmess" :key="doc.id">
            <span class="created-at">{{ doc.createdAt }}</span>
            <span class="name">{{ doc.name }}</span>
            <span class="message">{{ doc.message }}</span>
          </div>
          <form @submit.prevent="handleSubmit">
      <textarea
        placeholder="Type a message and hit enter to send..." 
        v-model="message"
      ></textarea>
      <button @click="Loading = true" class="bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-800 text-red-700 hover:text-neutral-400 hover:scale-105 text-xl w-64 p-4 rounded-xl shadow cursor-pointer inline-block m-10 transition ease-in-out duration-300 drop-shadow-2xl"
      >{{ Loading ? "Sending..." : "Send" }}</button>
      <div v-if="error" class="error">{{ error }} blah</div>
    </form>
        </div>

    </template>

I am not sure as to why the first Javascript file code isn't working, I've done the exact same thing to get the data with the only difference being that instead of doc(id) having the id argument for the route.params.id I`ve used

import { projectFirestore } from "../Firebase/Config";
import { ref } from "vue"
import { firebaseAuth } from "../Firebase/firebase";

const getChat = () => {
    const Chat = ref([])
    const error = ref(null)
    let lastDoc = null; 

    const load = async () => {
        try {
            let query = projectFirestore.collection('Subscribed').doc(firebaseAuth.currentUser.uid).collection('messages').orderBy('createdAt')
            if (lastDoc) {
                query = query.startAfter(lastDoc)
            }

            const res = await query.get()

            if (!res.empty) {
                lastDoc = res.docs[res.docs.length - 1]

                // Add new results to exsiting ones

                Chat.value = [...Chat.value, ...res.docs.map(doc => {
                  //  console.log(doc.data())
                    return {...doc.data(), id: doc.id}
                })]
            }
        }
        catch (err){
            error.value = err.message
           // console.log(error.value)
        }
    }

    return { Chat, error, load }
}

export default getChat

And this has worked just fine in the past, I first assumed that the route.params.id was not working properly but I was able to console.log it as well as remove everything after .doc(id) and I was able to see that the route.params was working fine. Does anyone know how to do this?


Solution

  • I typically use the onSnapshot method to listen for real-time updates.

    import { projectFirestore } from "../Firebase/Config";
    import { ref, watchEffect } from "vue";
    
       const getChatMess = (id) => {
        const ChatMess = ref([]);
        const error = ref(null);
    
    let collectionRef = projectFirestore.collection('Subscribed').doc(id).collection('messages').orderBy('createdAt');
    
    const unsub = collectionRef.onSnapshot(snap => {
    let results = [];
      snap.docs.forEach(doc => {
        doc.data().createdAt && results.push({ ...doc.data(), id: doc.id });
      });
      ChatMess.value = results;
      error.value = null;
    }, err => {
      console.log(err.message);
      ChatMess.value = null;
      error.value = 'could not fetch the data';
    });
    
    watchEffect((onInvalidate) => {
    onInvalidate(() => unsub());
    });
    
    return { ChatMess, error };
      };
    
      export default getChatMess;
    

    Also fix the typo in your script where Chatmess should be ChatMess and make sure that the Chatmess variable is correctly bound to the component template.

    <script>
    import { projectFirestore, timestamp } from '../Firebase/Config';
    import getChatMess from "../Composables/getChatMess";
    import { ref } from 'vue';
    import { useRoute } from 'vue-router';
    
    export default {
      setup() {
        const route = useRoute();
        const { ChatMess, error } = getChatMess(route.params.id);
    
        const message = ref('');
        const handleSubmit = async () => {
          const NewChatmess = {
            message: message.value,
            createdAt: timestamp(),
          };
          await projectFirestore.collection('Subscribed').doc(route.params.id).collection('messages').add(NewChatmess);
      message.value = ''; // Clear the input after sending the message
    };
    
    return { handleSubmit, message, ChatMess, error };
      }
    };
    </script>