I'm trying to write some Firestore operations into a separate package so that it could be imported and reused in different web apps. I'm building a monorepo with different packages and I'm trying to use Firebase v9 for the following example:
From packageA I'm defining and exporting a getPosts(db) function that takes in a Firestore object and returns some posts form the given database
// in 'packageA'
import { collection, getDocs, Firestore } from 'firebase/firestore';
export const getPosts = async (db: Firestore) => {
console.log('Passed in db: ', db); // This correctly prints the passed in Firestore object
try {
const postsCollection = collection(db, 'posts'); // This function will throw
const querySnapshot = await getDocs(postsCollection);
return querySnapshot.docs.map((doc) => doc.data());
} catch (e) {
console.error('Error reading posts: ', e);
}
}
In a web app I'm initialising the Firebase app and exporting the Firestore instance
// firebase.js in 'web-app-1'
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
const firebaseConfig = { /* my Firebase config */ };
export const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
Then I'm trying to use the getPosts function from the package in a component...
// App.js in 'web-app-1'
import { db } from './firebase.js';
import { getPosts } from 'packageA';
let posts;
async function loadPosts() {
try {
posts = await getPosts(db);
} catch (e) {
console.error(e);
}
}
loadPosts(); // throws an error
but I get the following error from the collection(db, 'posts') call
Error reading posts: Expected first argument to collection() to be a CollectionReference, a DocumentReference or FirebaseFirestore
even though the passed in database is correctly printed in the console (form the getPosts function)
Note: If I copy the whole getPosts function and use it directly in the web app (i.e. without importing it from another package) then it works and correctly fetches the posts.
It looks like a bug with version 9, and the method is trying to use a Firebase Realtime Database instead of Firestore, so the method is sending an error for the collections.
It seems to override the fact that it's Firestore when using the function, so I would send this to the Firebase support directly because the way that the package is being formed seems to be the main issue.
I've been looking around a bit more and found this answer to a similar question to solve my problem too.
Basically what I had to do is to specify Firebase as a peerDependency in packageA and not include it in the final bundle. The web apps that consume packageA will include Firebase as a regular dependency.
So the package.json files look as follows
In the utility package
{
"name": "packageA",
"peerDependencies": {
"firebase": "^9.6.3"
}
}
and then in the web apps
{
"name": "web-app-1",
"dependencies": {
"firebase": "^9.6.3",
}
}
This approach also makes sense to my use case as the web app – and only the web app – that initialises the Firebase app will include it in its bundle. I can imagine however that in some other use cases this is not a possible solution.
Nevertheless I have submitted my issue to the Firebase support as suggested and here is their answer:
We have received some similar cases and we are already working to solve this. However, it can take a while due the workload of the engineering team, please, be patient.
I am currently experience the same problem. The workaround is importing the files direct via the bundler.
Keep in mind this is not optimal because I have to install the packages in the native project again, so it requires some manual maintenance
This ensures that my app uses the firebase instance and package that is inside native/node_modules/
`
const { getDefaultConfig } = require("@expo/metro-config");
const path = require("path");
const projectRoot = __dirname;
const workspaceRoot = path.resolve(__dirname, "../..");
const config = getDefaultConfig(__dirname);
const extraNodeModules = {
'@aim/utils': path.resolve(__dirname + '/../../packages/utils'),
};
const watchFolders = [
path.resolve(__dirname + '/../../packages/utils')
];
config.watchFolders = [workspaceRoot];
config.resolver.nodeModulesPath = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
extraNodeModules: new Proxy(extraNodeModules, {
get: (target, name) =>
//redirects dependencies referenced from common/ to local node_modules
name in target ? target[name] : path.join(process.cwd(), `node_modules/${name}`),
}),
},
watchFolders,
};
// module.exports = config;
`
tsconfig.json `
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"jsx": "react-native",
"lib": ["dom", "esnext"],
"moduleResolution": "node",
"noEmit": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"strict": true,
"baseUrl": ".",
"paths": {
"@aim/utils/*": ["../../packages/utils/*"]
}
},
}
`