I have a utility function that works fine as long as I keep it in my codebase. It takes a CollectionReference
and a document id as arguments.
When I move it to a shared library I get the following error:
FirebaseError: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
The collection reference fails the instanceof
check that is done internally by firebase/firestore.
The code exported from my library is clean ESM and I have made sure that all places import from firebase/firestore
and use the same versions. So I don't know what else I can try.
Here is the code as it is exported by the library:
import { CollectionReference, doc, getDoc } from "firebase/firestore";
import { useEffect, useState } from "react";
function useDocumentDataOnce(collectionRef, documentId) {
const [data, setData] = useState();
useEffect(() => {
const fetchData = async () => {
if (!documentId) {
return;
}
console.log(
"+++ collectionRef instanceof CollectionReference?",
collectionRef instanceof CollectionReference
);
const ref = doc(collectionRef, documentId);
const snapshot = await getDoc(ref);
if (snapshot.exists()) {
setData(snapshot.data());
} else {
throw new Error(`No document at ${collectionRef.path}/${documentId}`);
}
};
fetchData().catch(console.error);
}, [collectionRef, documentId]);
return data;
}
If I use the exact same code, but imported from my codebase it works fine:
import { CollectionReference, doc, getDoc } from "firebase/firestore";
import { useEffect, useState } from "react";
export function useDocumentDataOnce<T>(
collectionRef: CollectionReference,
documentId?: string
) {
const [data, setData] = useState<T>();
useEffect(() => {
const fetchData = async () => {
if (!documentId) {
return;
}
console.log(
"+++ collectionRef instanceof CollectionReference?",
collectionRef instanceof CollectionReference
);
const ref = doc(collectionRef, documentId);
const snapshot = await getDoc(ref);
if (snapshot.exists()) {
setData(snapshot.data() as T);
} else {
throw new Error(`No document at ${collectionRef.path}/${documentId}`);
}
};
fetchData().catch(console.error);
}, [collectionRef, documentId]); // Add ref to the dependency array
return data;
}
Here's how the code is used on a Next.js page:
"use client";
import { db, useUserId } from "@/lib/firebase";
import { useDocumentDataOnce } from "failurebase";
import { collection } from "firebase/firestore";
type User = {
displayName: string;
};
export default function UserProfilePage() {
const userId = useUserId();
const usersCollectionRef = collection(db, "users");
const user = useDocumentDataOnce<User>(usersCollectionRef, userId);
return <div>Hello {user?.displayName ?? "unknown"}</div>;
}
The "use client" directive makes this page run only client-side in the browser.
The error comes from this line in useDocumentDataOnce
:
const ref = doc(collectionRef, documentId);
In the firebase-js-sdk library it comes down to this check on line 578 of reference.ts:
if (
!(parent instanceof DocumentReference) &&
!(parent instanceof CollectionReference)
) {
throw new FirestoreError(
Code.INVALID_ARGUMENT,
'Expected first argument to collection() to be a CollectionReference, ' +
'a DocumentReference or FirebaseFirestore'
);
}
You can find the library code here. As you can see I have also made firebase a peer dependency, so nothing is bundled with my library code.
The library is also published on NPM. You can install it with failurebase@next
This is driving me nuts. Any idea what might be causing this?
So I found that if I publish and install my module via NPM, it works.
The problem is not specific to Firebase, but the way (P)NPM linking works when developing modules locally. The instanceof
checks, that Firebase uses internally to validate a reference, will fail if you use normal linking.
I was passing a CollectionReference
instance across the module boundary to doc()
and the check in that function fails because both modules reference a different class prototype.
I have tried (p)npm link
as well as (p)npm add ../path/to/local/module
which creates a file:
link in package.json. With both approaches instanceof
returns false
.
A solution is to use yalc, which fortunately is easy.
Another solution would be if the Firebase team would replace the instanceof
check with a basic check on object properties. I think this is preferable, as it allows people to more easily develop libraries based on Firebase.
The check seems to be there mainly to warn the user for invalid use of the methods, so I think doing a check based on object properties should be sufficient.