reactfire: Firestore offline data access not working

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:

Unhandled Rejection (FirebaseError): 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.
....

The line causing this issue is here:

await enableIndexedDbPersistence(db);

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

  1. https://github.com/constantin-ungureanu-github/test

    • simple react app of the Burito quickstart example, where I also tried to use the offline data access.
  2. https://github.com/constantin-ungureanu-github/test-pwa-typescript

    • simple react app where I copied the examples from reactfire source code (non-concurrent ones), and the result is the same, enableIndexedDbPersistence produces the same exception.

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.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 8
  • Comments: 17 (5 by maintainers)

Most upvoted comments

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.

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;
  });