From 75c2eb6b8e05876c1014a138f4ba30c482ac7b67 Mon Sep 17 00:00:00 2001 From: Tyler Dixon Date: Fri, 10 Jul 2026 12:59:12 -0700 Subject: [PATCH 1/3] docs: fix emulator setup example to prevent double-initialization Calling connect*Emulator() in a React component body causes 'Firestore has already been started' errors on re-renders. Show two correct patterns: module-level initialization (preferred) and a useRef guard for component-level initialization. Fixes #459 --- docs/use.md | 61 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/docs/use.md b/docs/use.md index d65b9613..589d5467 100644 --- a/docs/use.md +++ b/docs/use.md @@ -97,32 +97,67 @@ SDK not found. useSdk must be called from within a provider ### Connect to the Firebase Local Emulator Suite -Connect a product SDK to the emulator before passing it to a provider. For example, to connect to the Auth and Realtime Database emulators: +Connect emulators to the SDK instances **before** passing them to providers, and ensure the connect calls only run once. Calling `connect*Emulator()` inside a React component body without a guard will throw on re-renders (`Firestore has already been started and its settings can no longer be changed`). + +The safest pattern is to initialize outside of React entirely: ```jsx -import { getAuth, connectAuthEmulator } from 'firebase/auth'; // Firebase v9+ -import { getDatabase, connectDatabaseEmulator } from 'firebase/database'; // Firebase v9+ +import { initializeApp } from 'firebase/app'; +import { getAuth, connectAuthEmulator } from 'firebase/auth'; +import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore'; -import { FirebaseAppProvider, DatabaseProvider, AuthProvider, useFirebaseApp } from 'reactfire'; +import { FirebaseAppProvider, FirestoreProvider, AuthProvider } from 'reactfire'; + +const app = initializeApp(firebaseConfig); +const auth = getAuth(app); +const firestore = getFirestore(app); + +// Check for dev/test mode however your app tracks that. +// `process.env.NODE_ENV` is a common React pattern +if (process.env.NODE_ENV !== 'production') { + connectAuthEmulator(auth, 'http://localhost:9099'); + connectFirestoreEmulator(firestore, 'localhost', 8080); +} + +function App() { + return ( + + + + + + + + ); +} +``` + +If you need to initialize inside a component (e.g. to access `useFirebaseApp()`), use a ref to ensure the connect calls only happen once: + +```jsx +import { useRef } from 'react'; +import { getAuth, connectAuthEmulator } from 'firebase/auth'; +import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore'; + +import { FirebaseAppProvider, FirestoreProvider, AuthProvider, useFirebaseApp } from 'reactfire'; function FirebaseComponents({ children }) { const app = useFirebaseApp(); - const database = getDatabase(app); const auth = getAuth(app); + const firestore = getFirestore(app); - // Check for dev/test mode however your app tracks that. - // `process.env.NODE_ENV` is a common React pattern - if (process.env.NODE_ENV !== 'production') { - // Set up emulators - connectDatabaseEmulator(database, 'localhost', 9000); + const connected = useRef(false); + if (!connected.current && process.env.NODE_ENV !== 'production') { + connected.current = true; connectAuthEmulator(auth, 'http://localhost:9099'); + connectFirestoreEmulator(firestore, 'localhost', 8080); } return ( - - - + + {children} + ); } From 6a58d1ff9df41f50694113ed4c43842a4c1b22f1 Mon Sep 17 00:00:00 2001 From: Tyler Dixon Date: Tue, 14 Jul 2026 13:29:53 -0700 Subject: [PATCH 2/3] docs: drop component-level useRef emulator example, recommend module-level init --- docs/use.md | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/docs/use.md b/docs/use.md index 589d5467..b5a54ab0 100644 --- a/docs/use.md +++ b/docs/use.md @@ -97,9 +97,7 @@ SDK not found. useSdk must be called from within a provider ### Connect to the Firebase Local Emulator Suite -Connect emulators to the SDK instances **before** passing them to providers, and ensure the connect calls only run once. Calling `connect*Emulator()` inside a React component body without a guard will throw on re-renders (`Firestore has already been started and its settings can no longer be changed`). - -The safest pattern is to initialize outside of React entirely: +Connect emulators to the SDK instances **before** passing them to providers. The safest pattern is to initialize outside of React entirely, which guarantees `connect*Emulator()` only runs once, regardless of re-renders or remounts: ```jsx import { initializeApp } from 'firebase/app'; @@ -132,37 +130,6 @@ function App() { } ``` -If you need to initialize inside a component (e.g. to access `useFirebaseApp()`), use a ref to ensure the connect calls only happen once: - -```jsx -import { useRef } from 'react'; -import { getAuth, connectAuthEmulator } from 'firebase/auth'; -import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore'; - -import { FirebaseAppProvider, FirestoreProvider, AuthProvider, useFirebaseApp } from 'reactfire'; - -function FirebaseComponents({ children }) { - const app = useFirebaseApp(); - const auth = getAuth(app); - const firestore = getFirestore(app); - - const connected = useRef(false); - if (!connected.current && process.env.NODE_ENV !== 'production') { - connected.current = true; - connectAuthEmulator(auth, 'http://localhost:9099'); - connectFirestoreEmulator(firestore, 'localhost', 8080); - } - - return ( - - - {children} - - - ); -} -``` - Learn more about the Local Emulator Suite in the [Firebase docs](https://firebase.google.com/docs/emulator-suite/connect_and_prototype). ### Set up App Check From 840f4bc015cfe76c64bc3621b511de752f8a318c Mon Sep 17 00:00:00 2001 From: Tyler Dixon Date: Tue, 14 Jul 2026 14:33:56 -0700 Subject: [PATCH 3/3] docs: note that firebaseConfig= prop makes emulator setup difficult, prefer firebaseApp= --- docs/use.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/use.md b/docs/use.md index b5a54ab0..5686a368 100644 --- a/docs/use.md +++ b/docs/use.md @@ -130,6 +130,8 @@ function App() { } ``` +This pattern requires passing `firebaseApp={app}` to `FirebaseAppProvider` so you own the app instance at module level. If you are currently using `firebaseConfig={...}` on `FirebaseAppProvider`, switch to initializing the app yourself with `initializeApp()` and passing it via `firebaseApp=`. The `firebaseConfig=` prop is a convenience shortcut, but it gives reactfire ownership of the app instance, which makes emulator setup (and anything else that needs the app before React renders) unnecessarily difficult. + Learn more about the Local Emulator Suite in the [Firebase docs](https://firebase.google.com/docs/emulator-suite/connect_and_prototype). ### Set up App Check