Open constantin-ungureanu-github opened 3 years ago
This is probably also related.
const { status: useInitFirestoreStatus, data: firestore } = useInitFirestore(
async (firebaseApp) => {
const firestoreInit = getFirestore(firebaseApp);
if (isDev) connectFirestoreEmulator(firestoreInit, 'localhost', 8080);
return firestoreInit;
}
);
It looks like the code is being run after every other useInit... completes.
@epodol I think your issue may be different. getFirestore
initializes Firestore with defaults. Do you see that behavior if you use initializeFirestore
instead? (I believe it's the only product that behaves this way)
Yes, this still occurs with initializeFirestore
. With a console.log inside the callback, it gets called once for each of the init callbacks defined.
Correction, my prior problem has been resolved by adding { suspense: false }
to each of the useInit calls.
However, when enabling offline persistence, I get a similar effect to the one described, except for me, the page load loads forever:
const { status: useInitFirestoreStatus, data: firestore } = useInitFirestore(
async (firebaseApp) => {
const firestoreInit = initializeFirestore(firebaseApp, {});
await enableIndexedDbPersistence(firestoreInit);
if (isDev) connectFirestoreEmulator(firestoreInit, 'localhost', 8080);
return firestoreInit;
},
{ suspense: false }
);
Interesting, thanks for the follow up @epodol. Having to add { suspense: false }
to each call sounds like a bug (the useInit
hooks should pick that up from Context).
I'll look into both of these this week.
Interesting, thanks for the follow up @epodol. Having to add
{ suspense: false }
to each call sounds like a bug (theuseInit
hooks should pick that up from Context).I'll look into both of these this week.
@jhuleatt Sorry, to clarify, I am using suspense globally, but I must not have been using it correctly with the useInit
hooks.
I'm investigating this in #451. I wrote a test that confirms that each useInitSdk
hook is called, but it's passing at the moment, so not too helpful. Will continue trying to reproduce the issue with @constantin-ungureanu-github's samples.
Hi 👋
I'm having the same issue in my tiny pet project. Hope my gist could help to reproduce: https://gist.github.com/Gladkov-Art/74e3e2073f0fc0db5e772c9ef02c22e2
"reactfire": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/reactfire/-/reactfire-4.2.0.tgz",
"integrity": "sha512-OfZyp2TNLZKxpejYqT/SSJCNjtAU3qyVhWbcfQZMG/LForown67QBk7X4Yn6O6vnoQ/RHyGvgtgnFL2rXbjb5Q==",
"requires": {
"rxfire": "^6.0.2",
"rxjs": "^6.6.3 || ^7.0.1"
}
},
"node_modules/firebase": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/firebase/-/firebase-9.0.0.tgz",
"integrity": "sha512-atgnuvELhU9D5w9moChnyCb6GRbOCqk54/kHN0J4kdLJBncpcb2culIJ7nlSHILMcW9MNMiNKDJ07RwXVyqFFA==",
"dependencies": {
"@firebase/analytics": "0.7.0",
"@firebase/analytics-compat": "0.1.0",
"@firebase/app": "0.7.0",
"@firebase/app-check": "0.4.0",
"@firebase/app-check-compat": "0.1.0",
"@firebase/app-compat": "0.1.0",
"@firebase/app-types": "0.7.0",
"@firebase/auth": "0.17.0",
"@firebase/auth-compat": "0.1.0",
"@firebase/database": "0.12.0",
"@firebase/database-compat": "0.1.0",
"@firebase/firestore": "3.0.0",
"@firebase/firestore-compat": "0.1.0",
"@firebase/functions": "0.7.0",
"@firebase/functions-compat": "0.1.0",
"@firebase/installations": "0.5.0",
"@firebase/messaging": "0.9.0",
"@firebase/messaging-compat": "0.1.0",
"@firebase/performance": "0.5.0",
"@firebase/performance-compat": "0.1.0",
"@firebase/polyfill": "0.3.36",
"@firebase/remote-config": "0.2.0",
"@firebase/remote-config-compat": "0.1.0",
"@firebase/storage": "0.8.0",
"@firebase/storage-compat": "0.1.0",
"@firebase/util": "1.3.0"
}
},
Update: it seems that issue is reproducible only in local development environment...
@jhuleatt - any luck in finding a solution for this issue? I am not able to get offline persistence working and I am getting the same exception as OP
Connect to emulator before enable Persistence works
const { status, data: firestore } = useInitFirestore(async (firebaseApp) => {
const db = initializeFirestore(firebaseApp, {});
connectFirestoreEmulator(db, "localhost", 8080);
await enableIndexedDbPersistence(db);
return db;
});
I have the same exception too (on reactfire@4.2.2) and connecting to emulator before enabling persistence does not work
Finally managed to find a workaround which is:
const { status, data: firestoreInstance } = useInitFirestore(async (firebaseApp) => {
const fsSettings = (process.env.NODE_ENV === 'development') ? { host: 'localhost:5080', ssl: false } : {};
const db = initializeFirestore(firebaseApp, fsSettings);
try {
await enableIndexedDbPersistence(db);
} catch (err) {
console.log(err)
}
return db;
});
It looks like persistence is being enabled, it's just being called multiple times hence the error we are seeing.
with the below snippet (much excluded) persistence is enabled on load shown by the log message however you will still see 2 messages as such
failed-precondition
App.tsx:## Firestore has already been started and persistence can no longer be enabled. You can only enable persistence before calling any other methods on a Firestore object.
import { enableIndexedDbPersistence, initializeFirestore } from 'firebase/firestore'
import { FirestoreProvider, useInitFirestore } from 'reactfire'
function App() {
const { data: firestoreInstance } = useInitFirestore(async (firebaseApp) => {
const db = initializeFirestore(firebaseApp, {})
try {
await enableIndexedDbPersistence(db)
console.log('indexdb enabled')
} catch (error) {
console.log(error.code)
console.log(error.message)
}
return db
})
return (
<FirestoreProvider sdk={firestoreInstance}>
<App />
</FirestoreProvider>
)
}
export default App
One of the causes of the double initialization is the use of <React.StrictMode>
which causes components to be mounted twice in dev mode, so if you are initializing firestore when a component mounts or inside a hook it will almost certainly happen twice (locally anyway).
I haven't found a good way to get useInitFirestore
working with strict mode. To avoid the issues with the double-render I'm using a custom hook with a global variable that tracks whether the initialization has already occurred. Here is the content of the hook (useInitializeFirestore.ts
):
import {useEffect, useState} from 'react';
import {FirebaseError} from 'firebase/app';
import {Firestore, enableMultiTabIndexedDbPersistence, initializeFirestore} from 'firebase/firestore';
import {useFirebaseApp} from 'reactfire';
// Use a global to ensure that we only initialize firestore once when running with React.StrictMode:
let FirestorePromise: Promise<Firestore>;
interface UseInitializeFirestore {
error?: FirebaseError;
firestore?: Firestore;
}
export function useInitializeFirestore(): UseInitializeFirestore {
const firebase = useFirebaseApp();
const [firestore, setFirestore] = useState<Firestore>();
const [error, setError] = useState<FirebaseError>();
const configureFirestore = async (): Promise<Firestore> => {
const fs = initializeFirestore(firebase, {});
try {
await enableMultiTabIndexedDbPersistence(fs);
} catch (error) {
setError(error as FirebaseError);
}
return fs;
};
if (!FirestorePromise) {
FirestorePromise = configureFirestore();
}
useEffect(() => {
const waitForFirestore = async () => {
setFirestore(await FirestorePromise);
};
waitForFirestore();
}, [setFirestore]);
return {error, firestore};
}
Now call the hook from a component (note that ErrorCard
and FullScreenLoading
are components specific to my project for displaying errors and a loading spinner):
type WithFirestoreProps = {
children: React.ReactNode
};
const WithFirestore: React.FC<WithFirestoreProps> = ({children}: WithFirestoreProps) => {
const {error, firestore} = useInitializeFirestore();
if (error) {
return <ErrorCard error={error} />;
}
if (!firestore) {
return <FullScreenLoading />;
}
return <FirestoreProvider sdk={firestore}>{children}</FirestoreProvider>;
};
Hope this helps someone else.
I got this working with the following:
// utils/firestore.ts
export const getInitializedFirestore = () => {
const firestore = getFirestore();
// only enable emulator if in dev mode, and we haven't already enabled it
if (
process.env.NODE_ENV === 'development' &&
(firestore as any)._getSettings().host === 'firestore.googleapis.com'
) {
connectFirestoreEmulator(getFirestore(), '127.0.0.1', 8080);
console.log('firestore emulator attached');
}
// only enable indexeddb if the firestore client hasn't been fully initialized
if (!(firestore as any)._firestoreClient) {
enableMultiTabIndexedDbPersistence(firestore);
console.log('persistence enabled');
}
return firestore;
};
Replace any calls to getFirestore()
with getInitializedFirestore()
. This is also HMR and page refresh safe, so dev mode is pleasant, and it can safely be deployed without having to touch anything.
I don't know why the Firebase SDK doesn't make these properties visible, but this lets you check the status without keeping track of global variables.
Looks like part of this problem is the <FirebaseAppProvider>
rerenders at least twice with different values for app
(different object identity). Since all the other SDK instances are memoized on the app
object identity, this causes e.g. useInitFirestore()
to rerun each time the FirebaseAppProvider
's app instance changes
Following the guides for using reactfire, I have tried enable offline access to data https://github.com/FirebaseExtended/reactfire/blob/main/docs/use.md#access-data-offline .
However, this is not working, and I get next exception:
The line causing this issue is here:
Removing previous line and the problem is solved, however, by doing so the offline access is bypassed.
Version info
Latest versions for react, firebase, reactfire.
Test case
In order to reproduce this issue, I have created 2 GitHub repositories
https://github.com/constantin-ungureanu-github/test
https://github.com/constantin-ungureanu-github/test-pwa-typescript
Steps to reproduce
Simply run the react examples described above. npm install npm start
Expected behaviour
Guide for data offline access to work as described.
Actual behaviour
Enabling offline access is not working, exception is thrown.