skip to Main Content

I’m working with Firestore Web SDK v9 in an NodeJS environment. I do not use the live update but query data when needed.

My data is held in a local variable entries. After creating new entry in the Firestore collection entries, I try fetch all docs from this collection to update the local variable. But the newly added entry does not show up:

/// scaffolding:

import { initializeApp } from "firebase/app"
import { getFirestore, collection, addDoc, getDocs, query } from "firebase/firestore"

const app = initializeApp({
  apiKey: "...",
  authDomain: "...",
  projectId: "...",
  storageBucket: "...",
  messagingSenderId: "...",
  appId: "...",
})
const db = getFirestore(app)


/// payload:

let entries = []

try {

  // add doc to collection:

  const docRef = await addDoc(collection(db, "entries"), { foo: "bar" })
  console.log(docRef.id) // will print the id as expected
  
  // fetch all docs in collection:

  const q = query(collection(db, "entries")
  const querySnapshot = await getDocs(q)

  const result = []
  querySnapshot.forEach((doc) => {
    result.push({
      _id: doc.id,
      ...doc.data()
    })
  }
  entries = result // this will only contain the docs that have already existed BEFORE the addDocs() operation above

} catch (error) {
  console.log(error) // no error is being logged, so the above seems to work as expected
}

If I add a timeout of a couple of seconds between the add doc and fetch docs section, the local entries variable is populated as expected:

// add doc section

await new Promise((resolve) => {
  setTimeout(() => {
    resolve()
  }, 5000)
})

// fetch docs section

entries = result // will contain the newly created doc along with those that have already existed

I am confused by this behavior as the documentation for addDoc() states that the function will resolve after the newly created doc has been written to the backend:

Returns:

Promise<DocumentReference>

A Promise resolved with a DocumentReference pointing to the newly created document after it has been written to the backend (Note that it won’t resolve while you’re offline).

My app (it’s running in the browser) is clearly online, and if it would be offline, the Promise should not resolve at all.

Does Firebase take additional time to process the new document? How can I wait for it to be available?


Edit: the issue I described above was with the real Firestore environment. But the same thing happenes whe I run tests in the Firestore emulator as well:

import {
    initializeTestEnvironment,
    assertFails,
    assertSucceeds,
} from "@firebase/rules-unit-testing"

import {
    doc,
    getDoc,
    setDoc,
    updateDoc,
    deleteDoc
} from "firebase/firestore"

import fs from "fs"
import path from "path"

import "mocha"

const PROJECT_ID = "workmatefinder-dev-7f1a5"

let testEnv

describe("Reading groups", () => {
    beforeEach(async () => {
        testEnv = await initializeTestEnvironment({
            projectId: PROJECT_ID,
            firestore: {
                host: "127.0.0.1",
                port: "8080",
                rules: fs.readFileSync(new URL(path.join(import.meta.url, "./firestore.rules")), "utf-8")
            }
        })

        await testEnv.withSecurityRulesDisabled(async (context) => {
            await setDoc(doc(context.firestore(), "groups", "alices"), {
                name: "alice's group",
                ownerId: "alice",
                members: [ "alice" ]
            })
        })
    })

    it("user is allowed to read groups", async () => {
        const dbBob = testEnv.authenticatedContext("bob").firestore()
        await assertSucceeds(getDoc(doc(dbBob, "groups", "alices")))
    })

    afterEach(async () => {
        await testEnv.clearFirestore()
    })
})

This occasionally (but not all the time) fails with FirebaseError: false for 'get' @ Lx, Null value error. for 'get' @ Ly.

If I add this at the end of beforeEach, the error is gone:

        // to avoid "null value errors" (which means that the above "awaits" don't work)
        await new Promise((resolve) => {
            setTimeout(() => {
                resolve()
            }, 1000)
        })

So it’s again the same issue, that setDoc does seem to resolve before the document is actually created.

2

Answers


  1. Chosen as BEST ANSWER

    A more elegant way to do this, which circumvents the issue completely, is to just push the object that was written to the doc to the local array:

    let entries = []
    
    const newEntry = { foo: "bar" }
    await addDoc(collection(db, "entries"), newEntry)
    
    entries.push(newEntry)
    

    Another way that - surprisingly! - works is to not fetch the whole collection but only the newly created doc:

    const entries = []
    
    const docRef = await addDoc(collection(db, "entries"), { foo: "bar" });
    const docSnapshot = await getDoc(docRef);
            
    entries.push(docSnapshot.data());
    

    I don't know why this works (while fetching the whole collection right after creating the doc will not return that doc as part of the result), but it works ...


  2. Well I’m not sure why is that exactly happening but you can avoid this behavior by using onSnapshot feature on your collection like this:

    import { onSnapshot } from "firebase/firestore";
    
    const unsub = onSnapshot(collection(db, "groups"), (querySnapshot) => {
      console.log("Updated Docs: ", querySnapshot.docChanges);
      console.log("Docs: ", querySnapshot.docs);
    });
    

    By doing this, you can be informed whenever your docs on that collection being changed.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search