firebase / firebaseui authentication error in reactjs SPA: u[v] is not a function

79 Views Asked by At

There is an error when i try to login with any provider using firebase.

In browser, somehow firebase recovers from this error and im authenticated after 10 seconds. On a mobile device, this takes even longer..

I get the error:

api.js?onload=__iframefcb695643:29 Uncaught TypeError: u[v] is not a function
    at Q.<computed> [as loaded_0] (api.js?onload=__iframefcb695643:29:145)
    at cb=gapi.loaded_0?le=scs:1:6

I believe this error causes the whole authentication code to crash... for example the auth.onAuthStateChanged does not return a user, even though the collect call has succeeded (eg via google oauth https://identitytoolkit.googleapis.com/v1/accounts:lookup)

Could this be a bug in firebase? Or could it be a timing / import issue?

I have tried different versions of firebase, importing firebase in the index versus inline using npm package, but the problem stays the same..

any ideas would be most helpful!

im using firebase with firebaseui in compat mode in a reactjs SPA. "firebase": "^10.7.1", "firebaseui": "^6.1.0",

This is how i setup firebase:

import firebase from 'firebase/compat/app';
import 'firebase/compat/auth';
import * as firebaseui from 'firebaseui';

import { User, onAuthStateChanged, EmailAuthProvider, GoogleAuthProvider, OAuthProvider } from 'firebase/auth';

const firebaseConfig = {
  ...
};

const app = firebase.initializeApp(firebaseConfig);

const auth = firebase.auth();
auth.languageCode = 'nl';

export { app,  auth };

then i use the following function in some react page (eg home.tsx) to load the ui:


  const setupFirebaseAuthUI = () => {
    const ui = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(auth);
    const providerApple = new OAuthProvider('apple.com');
    const uiConfig = {
      signInOptions: [EmailAuthProvider.PROVIDER_ID, GoogleAuthProvider.PROVIDER_ID, providerApple.providerId],
      callbacks: {
        signInSuccessWithAuthResult: authResult => {
          navigate('/');
          return false;
        },
      },
      privacyPolicyUrl: 'https://example.nl/privacy',
      tosUrl: 'https://example.nl/voorwaarden',
    };

    ui.start('#firebaseui-auth-container', uiConfig);
  };

1

There are 1 best solutions below

0
Peter F On

So i found something that may be the solution. Because of how react works, somehow the ui is loaded twice instead of once.

Only the first render of firebaseui will load the token from the redirect.. So when the second one starts, it has no token to receive anymore and it acts as a fresh resetted ui.

Solution is to set a ref when the first UI is loaded, so it is not allowed to load the ui twice in the login component.

I've setup an example with react create app and this is working code:

LoginPage.tsx

import React, { useEffect, useRef, useState } from "react";
import * as firebaseui from "firebaseui";
import "firebaseui/dist/firebaseui.css";
import { auth } from "./firebaseConfig";
import {
  EmailAuthProvider,
  GoogleAuthProvider,
  OAuthProvider,
} from "firebase/auth";

const LoginPage = () => {
  const [user, setUser] = useState<any | null>(null);

  // tracks if FirebaseUI has been initialized
  const firebaseUIInitialized = useRef(false);

  useEffect(() => {
    // track auth state
    const unsubscribeOnAuthStateChanged = auth.onAuthStateChanged(
      (currentUser) => {
        // https://firebase.google.com/docs/reference/js/auth.user
        setUser(currentUser);
      },
      (error) => {
        console.error("useFirebaseAuth:", error);
      }
    );

    // setup firebase
    setupFirebaseAuthUI();

    return () => {
      unsubscribeOnAuthStateChanged();
    };
  }, []);

  const setupFirebaseAuthUI = () => {
    if (!firebaseUIInitialized.current) {
      const ui =
        firebaseui.auth.AuthUI.getInstance() ||
        new firebaseui.auth.AuthUI(auth);
      const uiConfig = {
        signInOptions: [
          EmailAuthProvider.PROVIDER_ID,
          GoogleAuthProvider.PROVIDER_ID,
          new OAuthProvider("apple.com").providerId,
        ],
        callbacks: {
          // eslint-disable-next-line object-shorthand
          signInSuccessWithAuthResult: (authResult: any) => {
            // eg redirect using the router
            return false;
          },
        },
        privacyPolicyUrl: "https://kaartopia.nl/privacy",
        tosUrl: "https://kaartopia.nl/voorwaarden",
      };

      ui.start("#firebaseui-auth-container", uiConfig);
      // Mark that FirebaseUI has been initialized
      firebaseUIInitialized.current = true;
    }
  };

  const logout = () => {
    auth.signOut();
    window.location.reload();
  };

  return (
    <div>
      <p>User: {user?.uid}</p>
      {user?.uid && <button onClick={logout}>Sign out</button>}
      <div id="firebaseui-auth-container" />
    </div>
  );
};
export default LoginPage;

you can change the implementation of signInSuccessWithAuthResult and/or onAuthStateChanged accordingly.

firebaseConfig.tsx

import firebase from "firebase/compat/app";
import "firebase/compat/auth";

const firebaseConfig = require("./firebaseConfig.json");

const app = firebase.initializeApp(firebaseConfig);
const auth = app.auth();
auth.languageCode = "nl";

auth.onAuthStateChanged((user) => {
  console.log("firebaseConfig onAuthStateChanged:", user);
});

export { app, auth };