Skip to content

getReactNativePersistence breaks React Native Fast Refresh #6231

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Nantris opened this issue May 5, 2022 · 28 comments
Closed

getReactNativePersistence breaks React Native Fast Refresh #6231

Nantris opened this issue May 5, 2022 · 28 comments

Comments

@Nantris
Copy link

Nantris commented May 5, 2022

Describe your environment

  • Operating System version: Windows 7
  • Browser version: N/A
  • Firebase SDK version: 9.6.7
  • Firebase Product: Auth

Describe the problem

[email protected] Auth requires getReactNativePersistence on React Native - but including the relevant code for getReactNativePersistence causes the auth listener logic in our app to fire every time a change is made, crashing our app (without apparent error message) and preventing us from using Fast Refresh at all.

Steps to reproduce:

Unsure of full steps that may be required. For us, including the code below triggers it in logic that otherwise worked in [email protected] and in [email protected] for a while.

Relevant Code:

  initializeAuth(firebaseApp, {
    persistence: getReactNativePersistence(AsyncStorage),
  });
@Nantris
Copy link
Author

Nantris commented May 6, 2022

It may be that this only occurs during debugging but I can't confirm that at the moment now due to a major overhaul in our tooling.

@jbalidiong jbalidiong added the v9 label May 6, 2022
@ishmaelmoreno
Copy link

@slapbox does this still happen for you?

@Nantris
Copy link
Author

Nantris commented May 31, 2022

I think no, but I'm not sure of that.

I don't think the possible resolution is related to any change in Firebase though, but rather that we abandoned react-native-debugger in favor of Flipper.

@Nantris
Copy link
Author

Nantris commented Jun 3, 2022

Yes, it does still happen, even with Flipper.

Firebase: Error (auth/already-initialized).

@sam-gc
Copy link
Contributor

sam-gc commented Jun 6, 2022

The problem is that initializeAuth and getAuth can be called multiple times but only if the underlying dependencies don't change. For example, calling initializeAuth({persistence: browserLocalPersistence}); 10 times won't throw auth/already-initialized, but calling initializeAuth({persistence: browserLocalPersistence}); initializeAuth({persistence: browserSessionPersistence}); will throw the error since the persistence dependency has changed.

In this particular case, getReactNativePersistence returns a new object every time its called, so it appears initializeAuth is being called with different dependencies. It may be possible to avoid this behavior, but I can't give any promises as to timeline. In the meantime, you may be able to avoid this problem by disabling fast refresh specifically in the file that calls initializeAuth (e.g. vercel/next.js#13268 (comment))

@Nantris
Copy link
Author

Nantris commented Jun 6, 2022

Thanks for your reply @sam-gc.

Our file that calls initializeAuth has no React exports, so I think that the workaround you linked probably cannot work for us.

This is the full file that calls initializeAuth in our app looks like below, and the dependencies for initializeAuth shouldn't change - unless the firebaseApp argument is changed? firebaseApp is the output of initializeApp(config).

When the app is reloaded, initializeApp(config) presumably gets called anew and passed to initializeAuth:

import { initializeAuth } from 'firebase/auth';
import { getReactNativePersistence } from 'firebase/auth/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

export const afterFirebaseInitialize = firebaseApp => {
  initializeAuth(firebaseApp, {
    persistence: getReactNativePersistence(AsyncStorage),
  });
};

@sam-gc
Copy link
Contributor

sam-gc commented Jun 6, 2022

When this gets reloaded, getReactNativePersistence(AsyncStorage) will return a different object -- that's where the issue lies. firebaseApp shouldn't be changing, since initializeApp similarly returns the same object for the same dependencies

@Nantris
Copy link
Author

Nantris commented Jun 6, 2022

It shouldn't get reloaded as a new object in the code we have, but it could be - I assume must be if the only possible cause for this issue is initializeAuth being called anew. But like I said, it shouldn't be. We never unset firebaseApp once it's set.

It's hard to know for sure though because the issue doesn't occur 100% of the time now with Flipper as our debugging tool (as opposed to react-native-debugger) and the quirks of React Native debugging.

Our code looks like this:

let firebaseApp;

const makeFirebase = config => {
  if (firebaseApp) return firebaseApp;

  firebaseApp =
    typeof global.window !== 'undefined' ? initializeApp(config) : null;

  if (typeof afterFirebaseInitialize === 'function')
    afterFirebaseInitialize(firebaseApp);

  return firebaseApp;
};

@sam-gc
Copy link
Contributor

sam-gc commented Jun 13, 2022

That's right -- this error can only be thrown if initializeAuth is called twice, with different objects in the dependencies. Fixing this feature for React Native Fast Refresh is not on our roadmap at the moment, but we can keep this issue open to track the request

@Nantris
Copy link
Author

Nantris commented Jun 13, 2022

@sam-gc why would the line if (firebaseApp) return firebaseApp; not prevent double initialization? I assume there's no code that would cause the results of initializeApp(config) to be cleared, and that's what's stored in firebaseApp.

Something doesn't add up unless there's more to the story here.

@Nantris
Copy link
Author

Nantris commented Sep 22, 2022

This remains an issue and we're definitely not initializing anything twice. It's painstaking to be without Fast Refresh, and it's apparent we're not the only ones with the issue based on the emojis on @sam-gc's reply.

It only began with getReactNativePersistence and there is exactly one place that initializeAuth is being called, and that's where we're defining the persistence method.

We call initializeApp before initializeAuth, but we certainly don't call initializeAuth any more than once.

This is it. This is the sole place it's called. I have went over this back when I opened the issue and again now, and there's nothing on our end that's not being done according to the way recommended in the docs.

export const afterFirebaseInitialize = firebaseApp => {
  initializeAuth(firebaseApp, {
    persistence: getReactNativePersistence(AsyncStorage),
  });
};

I'd appreciate it so much if this was given some attention.


Our codebase is used on Electron and on React Native.

  • We don't need to call initializeAuth explicitly on Electron, and Fast Refresh works there.
  • We do need to call initializeAuth explicitly on React Native to set the persistence method, and Fast Refresh fails there.

In this particular case, getReactNativePersistence returns a new object every time its called, so it appears initializeAuth is being called with different dependencies.

@sam-gc as you can see in oru code example, this is not the case unless again the problem relies with Firebase - or with AsyncStorage, but that's passed to getReactNativePersistence, which again is on Firebase's end.

@ishmaelmoreno
Copy link

I've avoided updating my version of firebase due to this issue. I hope it is resolved soon because it defeats the purpose of using react native.

@Nantris
Copy link
Author

Nantris commented Sep 28, 2022

Friendly bump @sam-gc.

I hope this will not go neglected. It's hard for me to fathom fixing a newly introduced bug not being "on the roadmap." An undocumented regression without a workaround is a bug that needs fixing in my book.

@Nantris
Copy link
Author

Nantris commented Sep 28, 2022

If it's true that the explanation for this is users double invoking initializeAuth or getReactNativePersistence then this code should work, but it doesn't:

import { initializeApp } from 'firebase/app';
import { initializeAuth } from 'firebase/auth';
import { getReactNativePersistence } from 'firebase/auth/react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

let initialized = false;

const persistence = {
persistence: getReactNativePersistence(AsyncStorage),
};

export const afterFirebaseInitialize = firebaseApp => {
if (initialized) return;
initialized = true;
initializeAuth(firebaseApp, persistence);
};

let firebaseApp;

const makeFirebase = config => {
if (firebaseApp) return firebaseApp;

firebaseApp = initializeApp(config);
afterFirebaseInitialize(firebaseApp);
};

The explanation that this is a React Fast Refresh problem would hold water if this didn't only affect Firebase on React Native. No other projects on any other platform break like this, and crucially, nor did Firebase used to break like this with version 8 and below.

Even more crucially, Firebase 9 used to work as intended for us. I'll have to go back and try to figure out more about why that may be - too late right now.

@hsubox76
Copy link
Contributor

hsubox76 commented Oct 7, 2022

Oh! I just notiiced you're importing from both firebase/auth and firebase/auth/react-native. I think the second is actually a full copy of the first with a few modifications for RN, so I think you want to do just:

import { initializeAuth, getReactNativePersistence } from 'firebase/auth/react-native';

and get rid of the firebase/auth line. I'm not sure if this will fix your issue but can you give a try and see if it does? If it does, of course it seems we have some docs work to address.

@google-oss-bot
Copy link
Contributor

Hey @slapbox. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@ishmaelmoreno
Copy link

Hi @slapbox did you try @hsubox76's suggestion?

@emmanuel-2u
Copy link

Just stumbled upon this thread and I happen to have the same code as @hsubox76 suggested. I imported initializeAuth from firebase/auth/react-native but the problem persists. During fast refresh, auth/already-initialized error gets thrown.

@hsubox76
Copy link
Contributor

Can you show all of your relevant code, including all Firebase imports and where you call initializeApp and initializeAuth?

@emmanuel-2u
Copy link

emmanuel-2u commented Oct 17, 2022

Code

import { initializeApp } from 'firebase/app';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
    initializeAuth,
    getReactNativePersistence
} from 'firebase/auth/react-native';

// Replace with real values
const firebaseConfig = {
    apiKey: "apiKey",
    authDomain: "authDomain",
    projectId: "projectId",
    storageBucket: "storageBucket",
    messagingSenderId: "messagingSenderId",
    appId: "appId"
};

const firebaseApp = initializeApp(firebaseConfig);

const firebaseAuth = initializeAuth(firebaseApp, {
    persistence: getReactNativePersistence(AsyncStorage)
});

@hsubox76
Copy link
Contributor

I'm not totally sure how React Native fast refresh works but it seems like the other user in this thread had to put in some kind of check to make sure that initializeAuth() doesn't get called a second time on refresh, while the previous instance still exists in memory. The other user's code (from above) was:

let initialized = false;

const persistence = {
persistence: getReactNativePersistence(AsyncStorage),
};

export const afterFirebaseInitialize = firebaseApp => {
if (initialized) return;
initialized = true;
initializeAuth(firebaseApp, persistence);
};

Again I'm not sure what's needed for React Native fast refresh, or what it keeps or resets when it refreshes. If you wanted to test it you could put a console.log right next to initializeAuth() and see if it gets called again when you refresh, and if so, it would seem like initializeAuth() is getting called again on refresh, which you don't want.

@Nantris
Copy link
Author

Nantris commented Oct 18, 2022

I haven't gotten a chance to try @hsubox76's suggestion because I'm pretty sure we can't use it. Our code is cross-platform, so I don't think we can easily implement the suggestion, but maybe via babel-module-resolver-plugin. I'll try to find time to test it in the near future.

@emmanuel-2u
Copy link

emmanuel-2u commented Oct 18, 2022

@hsubox76 I noticed the checks and I've tried it before commenting but it didn't work. I added the console.log and noticed it's getting called again.

However, this worked for me

import { getAuth, initializeAuth, getReactNativePersistence} from "firebase/auth/react-native";
import AsyncStorage from "@react-native-async-storage/async-storage";

if (getApps().length === 0) {
app = initializeApp(firebaseConfig);
auth = initializeAuth(app, {
persistence: getReactNativePersistence(AsyncStorage),
});

} else {
app = getApp();
auth = getAuth(app);
}

https://github.com/firebase/firebase-js-sdk/issues/6050#issuecomment-1119131377

@hsubox76
Copy link
Contributor

hsubox76 commented Nov 8, 2022

Sorry to hijack this thread but I'm trying to make sure we correct our documentation to be clear about how to use firebase/auth/react-native to prevent people from going through these headaches unnecessarily, and I don't seem to be able to find the documentation for it. Could anyone using that package tell me where you found out about firebase/auth/react-native? Was it just from Github issues or looking at the Firebase code itself, or is there a docs page somewhere I missed? If there aren't any docs I'll try to add some.

@Nantris
Copy link
Author

Nantris commented Nov 8, 2022

@hsubox76 thanks for your continued attention on this issue.

Could anyone using that package tell me where you found out about firebase/auth/react-native?

I first became aware of it from comments on issue #6050, which do link to some mentions in the API reference - specifically here and in comments below.

@tranlammedia
Copy link

tranlammedia commented Nov 11, 2022

this í my sovle. use {initializeAuth} from 'firebase/auth/react-native' replace for {getAuth} from "firebase/auth";

import AsyncStorage from '@react-native-async-storage/async-storage';
import { initializeApp } from 'firebase/app';
import {
  initializeAuth,
  getReactNativePersistence
} from 'firebase/auth/react-native';

// add firebase config here

// initialize firebase app
const app = initializeApp(firebaseConfig);

// initialize auth
const auth = initializeAuth(app, {
  persistence: getReactNativePersistence(AsyncStorage)
});

export { auth };

@martinivanalfonso
Copy link

This seems to be fixed on version 10.0.0

thank you @hsubox76

Sidenote for anyone following this thread, remove this fix if implemented as it seems to break fast refreshing otherwise

const auth = initializeAuth(app, { persistence: getReactNativePersistence(AsyncStorage) });

@hsubox76
Copy link
Contributor

Thanks for the update. I'll close this issue for now and add a link to the release notes for 10.0.0 to make it clear that

  1. We got rid of the special explicit path for the react-native bundle and added a react-native field to the package.json so that the react-native should be automatically chosen by the Metro bundler
  2. The AsyncStorage community package is now a dependency of firebase/auth and should now be the default if you call getAuth() in react native

https://firebase.google.com/support/release-notes/js#version_1000_-_july_6_2023

@firebase firebase locked and limited conversation to collaborators Aug 18, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests