Search code examples
javascriptfirebasegoogle-cloud-firestore

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


I am trying to create and admin chat where the admin can view all the user messages and then reply to them I have 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 have tried 2 methods one that 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 have done the exact same thing to get the data the only difference being that instead of doc(id) having the id argument for the route.params.id I have 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>