Firebase Firestore emulator error `Host has been set in both settings() and useEmulator(), emulator host will be used`
Asked Answered
F

4

15

first of all that is the full error I got.

@firebase/firestore: Firestore (8.1.1): Host has been set in both settings() and useEmulator(), emulator host will be used
Error [FirebaseError]: Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object.

this is how I init the emulator

const db = app.firestore();
const auth = firebase.auth();
if (process.env.NODE_ENV === 'development') {
  db.useEmulator('localhost', 8888);
  firebase.auth().useEmulator('http://localhost:9099/');
}

the project is running nextjs when I first start the application everything run as expected but after some refreshing or navigation among next.js pages, I suddenly get this error. and I have to kill the terminal and start over which is annoying I don't know if next.js server runs the if (process.env.NODE_ENV === 'development') code several times and this could be the cause of this error if that is case how to avoid setting a new emulator when there is one already. or is it a bug related to firebase emulators?.

Followthrough answered 30/11, 2020 at 1:17 Comment(5)
Do you enable Persistence parameter from your database settings? I found this GitHub issue where Persistence is being mentioned with a similar error.Xena
@ArtemisGeorgakopoulou no, I actually didn't add any custom settings I only using the useEmulator method but before that, I add the local host on development as this js firestore().settings({ host: 'http://localhost:8080', ssl: false }) but I had this error ``` Error [FirebaseError]: Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object. ``` very often when I used the useEmulator it happens but less often.Followthrough
can anyone above 1500 please suggest the Firebase-emulators tag to be used on StackOverflow?Followthrough
In the line " db.useEmulator('localhost', 8888); ", have you tried setting the port 8080 instead as configured in the settings?Xena
well 8080 is used by Postgres so I configured 8888 as the port for firestoreFollowthrough
F
13

after trying almost all of the solutions here it didn't really work the bug was happening from time to time the annoying thing is that I didn't know how to reproduce it but I think it happens when this page has a server-side error anyway the solution I used for getting around this bug was the following

const EMULATORS_STARTED = 'EMULATORS_STARTED';
function startEmulators() {
  if (!global[EMULATORS_STARTED]) {
    global[EMULATORS_STARTED] = true;
    firebase.firestore().useEmulator('localhost', 8888);
    firebase.auth().useEmulator('http://localhost:9099/');
  }
}

if (process.env.NODE_ENV === 'development') {
  startEmulators();
}

but for this to work like expected you will need to make sure that all emulators have started before making a request to the next.js server because if this code was executed before the emulators start then global[EMULATORS_STARTED] would be true and it will never use the emulators in this case. I have tested this on so many pages and some of them had server-side errors and the bug wasn't happening instead I got logs of these errors which is the expected behavior I didn't know these Errors existed in the first place before applying this solution 😁.

Followthrough answered 25/6, 2021 at 9:38 Comment(1)
I got a typescript error that stopped this from working for some reason but changed file to .js and now works. Thanks.Powell
R
6

NextJs is hot-reloading the web page, and xxx.useEmulator(...) is being called twice for the same browser instance.

Under the hood the Firebase library uses a global reference to the current app, and from the perspective of the library you're trying to initialize it twice or more.

You can reproduce this problem with the following code:

const db = app.firestore();
db.useEmulator('localhost', 8888);
db.useEmulator('localhost', 8888); // raises error

The only work-around that I've found is to use the window object to hold a flag if it's been initialized or not, but you also have to handle the edge case of SSR.

const db = app.firestore();
if(typeof window === 'undefined' || !window['_init']) {
   db.useEmulator('localhost', 8888);
   if(typeof window !== 'undefined') {
      window['_init'] = true;
   }
}

It's not the most elegant code above but it fixes the error.

The key is to know that hot reloading is the problem, and Firebase should only be configured once.

Rattray answered 10/3, 2021 at 15:21 Comment(2)
I would like to mark it as the solution but I think I will give it a day or more to ensure it was the solution because the bug I face isn't always happening. thanksFollowthrough
Can you explain why you db.useEmulator() when the code is being run on the server (typeof window === 'undefined')? I would think that we'd only want to initialize the emulator with client-side code? So far it appears that just connecting the firestore emulator client-side works well (if typeof window !== 'undefined')... unless I'm missing something?Decanter
L
3

Unfortunately, the accepted answer did not work for me. I ended up needing to use getApp even though I was memoizing my init function. Here is the code that finally fixed this issue for me (same context: Next.js app with hot reload):

const firebaseConfig = {...}

const initFirebase = once(() => {
  console.log('Initializing firebase')

  const app = initializeApp(firebaseConfig)
  const auth = getAuth(app)
  const functions = getFunctions(app)
  const db = getFirestore(app)

  if (process.env.NEXT_PUBLIC_FIREBASE_USE_EMULATOR === 'true') {
    console.log('Attching firebase emulators')

    connectFirestoreEmulator(db, 'localhost', 8080)
    connectAuthEmulator(auth, 'http://localhost:9299')
    connectFunctionsEmulator(functions, 'localhost', 5001)
  }
  return { auth, functions, db }
})

export const getFirebase = () => {
  try {
    const app = getApp()
    const auth = getAuth(app)
    const functions = getFunctions(app)
    const db = getFirestore(app)
    return { auth, functions, db }
  } catch (e) {
    return initFirebase()
  }
}
Lenka answered 22/5, 2022 at 23:18 Comment(0)
D
0

I agree with @Reactgular assessment about the cause. But in my case, the global solution is not well suited because the error is thrown during tests, and they should be isolated.

I also had to deal with a "clear emulator data" logic, that opposite to the useEmulator should be called at every initialization.

To solve that, I found that an inner "host" property inside the Firestore object changes after we call useEmulator. I then started to check that property before commanding the useEmulator.

Here is the code:

import { del } from '../request';

async function initFirestore (config) {
  const { suite, firestoreEmulatorHost } = config;
  const { app, projectId } = suite;

  const firestore = app.firestore();

  if (firestoreEmulatorHost) {
    plugEmulator(firestore, firestoreEmulatorHost);
    await clearFirestoreEmulator(projectId, firestoreEmulatorHost);
  }

  return firestore;
}

export function plugEmulator (firestore, firestoreEmulatorHost) {
  const settingsHost = firestore._delegate._settings.host;
  const isUnplugged = !settingsHost.includes(firestoreEmulatorHost);

  if (isUnplugged) {
    firestore.useEmulator('localhost', firestoreEmulatorHost);
  }
}

export function clearFirestoreEmulator (projectId, firestoreEmulatorHost) {
  const clearUrl = `http://localhost:${firestoreEmulatorHost}/emulator/v1/projects/${projectId}/databases/(default)/documents`;
  return del(clearUrl);
}
Disaccord answered 3/4, 2021 at 16:4 Comment(2)
what are the config values?Aulic
the config values are (1) suite which hold a instance of the firebase app and the firebase projectId and (2) the emulator host which a keep in a config file but really never changed from "8080".Raveaux

© 2022 - 2025 — McMap. All rights reserved.