Skip to content
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

Upgraded to React 18, updated dependencies, fixed "AuthUI instance is deleted" error #173

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

gvillenave
Copy link

  • Updated peer dependency to support React 18
  • Updated all dependencies (Babel, Webpack, ...)
  • Replaced React class component with function component
  • Updated component lifecycle management from componentWillMount/componentWillUnmount to a useEffect hook
  • Fixed React 18 StrictMode causes "AuthUI instance is deleted" error #172
  • Switched from using a hardcoded element ID to a React ref with useRef
  • Updated example app accordingly
  • Switched example app from using extract-text-webpack-plugin (now deprecated) to mini-css-extract-plugin and postcss
  • Updated README
  • Bumped package version to 7.0.0

@chuhancheng
Copy link

Hi, is it ready to be merged?

@modsushi
Copy link

When is it getting merged?

@gvillenave
Copy link
Author

@jhuleatt can you review and merge?

@jhuleatt jhuleatt self-requested a review May 18, 2022 14:18
@cuongnvn
Copy link

Hi all. I need this PR merged right now. Thank you

@aamancio
Copy link

Hey when is this fix getting merged?

@gvillenave
Copy link
Author

@jhuleatt any feedback on the PR? Can we get this merged soon?

@zettry1
Copy link

zettry1 commented Jun 1, 2022

waiting for merge

@paviln
Copy link

paviln commented Jun 3, 2022

waiting for merge +1

@MaurizioTonelli
Copy link

waiting for merge +2

@YGKtech
Copy link

YGKtech commented Jun 8, 2022

I too would greatly appreciate this getting merged

@MartinXPN
Copy link

MartinXPN commented Jun 9, 2022

React solution

Based on this PR, if anyone is using React (create-react-app and not Next.js or other) and is looking for a way of solving this issue, you can get rid of firebaseui-web-react and just use firebaseui.
These are the steps to do that:

  • Remove react-firebaseui (yarn remove react-firebaseui)
  • Add firebaseui (yarn add firebaseui)
  • Copy the below code as StyledFirebaseAuth.tsx
import { useEffect, useRef, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import * as firebaseui from 'firebaseui';
import 'firebaseui/dist/firebaseui.css';

interface Props {
    // The Firebase UI Web UI Config object.
    // See: https://github.com/firebase/firebaseui-web#configuration
    uiConfig: firebaseui.auth.Config;
    // Callback that will be passed the FirebaseUi instance before it is
    // started. This allows access to certain configuration options such as
    // disableAutoSignIn().
    uiCallback?(ui: firebaseui.auth.AuthUI): void;
    // The Firebase App auth instance to use.
    firebaseAuth: any; // As firebaseui-web
    className?: string;
}


const StyledFirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
    const [userSignedIn, setUserSignedIn] = useState(false);
    const elementRef = useRef(null);

    useEffect(() => {
        // Get or Create a firebaseUI instance.
        const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
        if (uiConfig.signInFlow === 'popup')
            firebaseUiWidget.reset();

        // We track the auth state to reset firebaseUi if the user signs out.
        const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, (user) => {
            if (!user && userSignedIn)
                firebaseUiWidget.reset();
            setUserSignedIn(!!user);
        });

        // Trigger the callback if any was set.
        if (uiCallback)
            uiCallback(firebaseUiWidget);

        // Render the firebaseUi Widget.
        // @ts-ignore
        firebaseUiWidget.start(elementRef.current, uiConfig);

        return () => {
            unregisterAuthObserver();
            firebaseUiWidget.reset();
        };
    }, [firebaseui, uiConfig]);

    return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;

That's it! You can use this component without the need for this library.
If you need FirebaseAuth instead of the StyledFirebaseAuth, you can just remove the import 'firebaseui/dist/firebaseui.css'; line from the component and you'll be good to go!

I just did this for my personal project and reduced the main bundle size by ~9KB.

@Skyblueballykid
Copy link

waiting for merge +3

@lewis-cobry
Copy link

me too!

@frontcodelover
Copy link

Please merge it :)

@th-m
Copy link

th-m commented Jun 23, 2022

merge 🙏

@IAmKio
Copy link

IAmKio commented Jun 25, 2022

Pretty please with chocolate sprinkles on top? 🙏

@Skyblueballykid
Copy link

Skyblueballykid commented Jul 6, 2022

Give me permission and I'll merge it. This project feels unmaintained.

@xhonorate
Copy link

waiting for merge +999

@gbourne1
Copy link

gbourne1 commented Jul 14, 2022

@jhuleatt

Can this be merged?

@wiktor-cobry
Copy link

Please merge this; no reason not to.

@karkir0003
Copy link

karkir0003 commented Aug 8, 2022

@jhuleatt and @Skyblueballykid , this merge is important for my team's project! Please be a kind soul and help us unblock. At least, by when should this PR get merged in?

@MartinXPN
Copy link

MartinXPN commented Aug 15, 2022

Next.js solution

For anyone looking for a solution for Next.js, you can use the following code and completely get rid of this library:

  • Remove react-firebaseui (yarn remove react-firebaseui)
  • Add firebaseui (yarn add firebaseui)
  • Copy the below code as StyledFirebaseAuth.tsx
import { useEffect, useRef, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import 'firebaseui/dist/firebaseui.css';
import {auth} from "firebaseui";

interface Props {
    // The Firebase UI Web UI Config object.
    // See: https://github.com/firebase/firebaseui-web#configuration
    uiConfig: auth.Config;
    // Callback that will be passed the FirebaseUi instance before it is
    // started. This allows access to certain configuration options such as
    // disableAutoSignIn().
    uiCallback?(ui: auth.AuthUI): void;
    // The Firebase App auth instance to use.
    firebaseAuth: any; // As firebaseui-web
    className?: string;
}


const StyledFirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
    const [firebaseui, setFirebaseui] = useState<typeof import('firebaseui') | null>(null);
    const [userSignedIn, setUserSignedIn] = useState(false);
    const elementRef = useRef(null);

    useEffect(() => {
        // Firebase UI only works on the Client. So we're loading the package only after
        // the component has mounted, so that this works when doing server-side rendering.
        setFirebaseui(require('firebaseui'));
    }, []);


    useEffect(() => {
        if (firebaseui === null )
            return;

        // Get or Create a firebaseUI instance.
        const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
        if (uiConfig.signInFlow === 'popup')
            firebaseUiWidget.reset();

        // We track the auth state to reset firebaseUi if the user signs out.
        const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, user => {
            if (!user && userSignedIn)
                firebaseUiWidget.reset();
            setUserSignedIn(!!user);
        });

        // Trigger the callback if any was set.
        if (uiCallback)
            uiCallback(firebaseUiWidget);

        // Render the firebaseUi Widget.
        // @ts-ignore
        firebaseUiWidget.start(elementRef.current, uiConfig);

        return () => {
            unregisterAuthObserver();
            firebaseUiWidget.reset();
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [firebaseui, uiConfig]);

    return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;

That's it! You can use this component without the need for this library.
If you need FirebaseAuth instead of the StyledFirebaseAuth, you can just remove the import 'firebaseui/dist/firebaseui.css'; line from the component and you'll be good to go!

@LeandroLeon
Copy link

Waiting for merge 🙏

@michaelangeloio
Copy link

pls merge!

@gbourne1
Copy link

React solution

Based on this PR, if anyone is using React (create-react-app and not Next.js or other) and is looking for a way of solving this issue, you can get rid of firebaseui-web-react and just use firebaseui. These are the steps to do that:

  • Remove react-firebaseui (yarn remove react-firebaseui)
  • Add firebaseui (yarn add firebaseui)
  • Copy the below code as StyledFirebaseAuth.tsx
import { useEffect, useRef, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import * as firebaseui from 'firebaseui';
import 'firebaseui/dist/firebaseui.css';

interface Props {
    // The Firebase UI Web UI Config object.
    // See: https://github.com/firebase/firebaseui-web#configuration
    uiConfig: firebaseui.auth.Config;
    // Callback that will be passed the FirebaseUi instance before it is
    // started. This allows access to certain configuration options such as
    // disableAutoSignIn().
    uiCallback?(ui: firebaseui.auth.AuthUI): void;
    // The Firebase App auth instance to use.
    firebaseAuth: any; // As firebaseui-web
    className?: string;
}


const StyledFirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
    const [userSignedIn, setUserSignedIn] = useState(false);
    const elementRef = useRef(null);

    useEffect(() => {
        // Get or Create a firebaseUI instance.
        const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
        if (uiConfig.signInFlow === 'popup')
            firebaseUiWidget.reset();

        // We track the auth state to reset firebaseUi if the user signs out.
        const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, (user) => {
            if (!user && userSignedIn)
                firebaseUiWidget.reset();
            setUserSignedIn(!!user);
        });

        // Trigger the callback if any was set.
        if (uiCallback)
            uiCallback(firebaseUiWidget);

        // Render the firebaseUi Widget.
        // @ts-ignore
        firebaseUiWidget.start(elementRef.current, uiConfig);

        return () => {
            unregisterAuthObserver();
            firebaseUiWidget.reset();
        };
    }, [firebaseui, uiConfig]);

    return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;

That's it! You can use this component without the need for this library. If you need FirebaseAuth instead of the StyledFirebaseAuth, you can just remove the import 'firebaseui/dist/firebaseui.css'; line from the component and you'll be good to go!

I just did this for my personal project and reduced the main bundle size by ~9KB.

Perfect and worked like a charm! Thanks.

@Sp3ctreZero
Copy link

Hey guys come on! Many people are looking for this, it is as simple as merging it, just do it or give access to someone else that will actually maintain it.

@michaelangeloio
Copy link

Next.js solution

For anyone looking for a solution for Next.js, you can use the following code and completely get rid of this library:

  • Remove react-firebaseui (yarn remove react-firebaseui)
  • Add firebaseui (yarn add firebaseui)
  • Copy the below code as StyledFirebaseAuth.tsx
import { useEffect, useRef, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import 'firebaseui/dist/firebaseui.css';
import {auth} from "firebaseui";

interface Props {
    // The Firebase UI Web UI Config object.
    // See: https://github.com/firebase/firebaseui-web#configuration
    uiConfig: auth.Config;
    // Callback that will be passed the FirebaseUi instance before it is
    // started. This allows access to certain configuration options such as
    // disableAutoSignIn().
    uiCallback?(ui: auth.AuthUI): void;
    // The Firebase App auth instance to use.
    firebaseAuth: any; // As firebaseui-web
    className?: string;
}


const StyledFirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
    const [firebaseui, setFirebaseui] = useState<typeof import('firebaseui') | null>(null);
    const [userSignedIn, setUserSignedIn] = useState(false);
    const elementRef = useRef(null);

    useEffect(() => {
        // Firebase UI only works on the Client. So we're loading the package only after
        // the component has mounted, so that this works when doing server-side rendering.
        setFirebaseui(require('firebaseui'));
    }, []);


    useEffect(() => {
        if (firebaseui === null )
            return;

        // Get or Create a firebaseUI instance.
        const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
        if (uiConfig.signInFlow === 'popup')
            firebaseUiWidget.reset();

        // We track the auth state to reset firebaseUi if the user signs out.
        const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, user => {
            if (!user && userSignedIn)
                firebaseUiWidget.reset();
            setUserSignedIn(!!user);
        });

        // Trigger the callback if any was set.
        if (uiCallback)
            uiCallback(firebaseUiWidget);

        // Render the firebaseUi Widget.
        // @ts-ignore
        firebaseUiWidget.start(elementRef.current, uiConfig);

        return () => {
            unregisterAuthObserver();
            firebaseUiWidget.reset();
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [firebaseui, uiConfig]);

    return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;

That's it! You can use this component without the need for this library. If you need FirebaseAuth instead of the StyledFirebaseAuth, you can just remove the import 'firebaseui/dist/firebaseui.css'; line from the component and you'll be good to go!

worked like a charm! Thank you 🤙

@rshekhtm
Copy link

rshekhtm commented Sep 14, 2022

This is fantastic, but I found that redirects (e.g. with Google SSO or Email Link) still do not work correctly in development due to React.StrictMode and the new React 18 "strict effects" feature forcing an unmount and remount after firebaseUiWidget.start(...) transitions from temporary redirect link to the sign-in URL. Couldn't figure out a clean solution here, so went with a hack that skips unmount/remount when redirection is in play.

    const skipStrictEffects = useRef(false);

    useEffect(() => {
        ...
        if (!skipStrictEffects.current) {
            skipStrictEffects.current = firebaseUiWidget.isPendingRedirect();
            firebaseUiWidget.start(elementRef.current, uiConfig);
        }

        return () => {
            if (!skipStrictEffects.current) {
                unregisterAuthObserver();
                firebaseUiWidget.reset();
            }
        };
    }, [firebaseui, uiConfig]);

@latenightpraj
Copy link

can we get this merged please?

@cgil
Copy link

cgil commented Sep 28, 2022

@gvillenave Is this ready to be merged?

Copy link

@joshiefishbein joshiefishbein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's merge <3

@memark
Copy link

memark commented Oct 6, 2022

Could we merge this please @jhuleatt?

@gbourne1
Copy link

Could we merge this please @jhuleatt?

I think this project is dead - 11 month since last update with many pleas to merge. I recommend removing the dependency as mentioned above.

@ababra
Copy link

ababra commented Nov 7, 2022

Since my project isn't using TSX and I had issues converting from TSX to JSX/JS, I ended up doing the following:

yarn remove react-firebaseui
yarn add https://github.com/gvillenave/firebaseui-web-react.git#acb47b46dc39682d13f2b117524bda95ec1aeddf

In my project, I am importing as follows:

import StyledFirebaseAuth from 'react-firebaseui/dist/StyledFirebaseAuth';

Not ideal, I know 👎

@oscar-gallog
Copy link

It is a shame that this project is dead

@JS-GitRepo
Copy link

JS-GitRepo commented Nov 19, 2022

Next.js solution

For anyone looking for a solution for Next.js, you can use the following code and completely get rid of this library:

  • Remove react-firebaseui (yarn remove react-firebaseui)
  • Add firebaseui (yarn add firebaseui)
  • Copy the below code as StyledFirebaseAuth.tsx
import { useEffect, useRef, useState } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import 'firebaseui/dist/firebaseui.css';
import {auth} from "firebaseui";

interface Props {
    // The Firebase UI Web UI Config object.
    // See: https://github.com/firebase/firebaseui-web#configuration
    uiConfig: auth.Config;
    // Callback that will be passed the FirebaseUi instance before it is
    // started. This allows access to certain configuration options such as
    // disableAutoSignIn().
    uiCallback?(ui: auth.AuthUI): void;
    // The Firebase App auth instance to use.
    firebaseAuth: any; // As firebaseui-web
    className?: string;
}


const StyledFirebaseAuth = ({uiConfig, firebaseAuth, className, uiCallback}: Props) => {
    const [firebaseui, setFirebaseui] = useState<typeof import('firebaseui') | null>(null);
    const [userSignedIn, setUserSignedIn] = useState(false);
    const elementRef = useRef(null);

    useEffect(() => {
        // Firebase UI only works on the Client. So we're loading the package only after
        // the component has mounted, so that this works when doing server-side rendering.
        setFirebaseui(require('firebaseui'));
    }, []);


    useEffect(() => {
        if (firebaseui === null )
            return;

        // Get or Create a firebaseUI instance.
        const firebaseUiWidget = firebaseui.auth.AuthUI.getInstance() || new firebaseui.auth.AuthUI(firebaseAuth);
        if (uiConfig.signInFlow === 'popup')
            firebaseUiWidget.reset();

        // We track the auth state to reset firebaseUi if the user signs out.
        const unregisterAuthObserver = onAuthStateChanged(firebaseAuth, user => {
            if (!user && userSignedIn)
                firebaseUiWidget.reset();
            setUserSignedIn(!!user);
        });

        // Trigger the callback if any was set.
        if (uiCallback)
            uiCallback(firebaseUiWidget);

        // Render the firebaseUi Widget.
        // @ts-ignore
        firebaseUiWidget.start(elementRef.current, uiConfig);

        return () => {
            unregisterAuthObserver();
            firebaseUiWidget.reset();
        };
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [firebaseui, uiConfig]);

    return <div className={className} ref={elementRef} />;
};

export default StyledFirebaseAuth;

That's it! You can use this component without the need for this library. If you need FirebaseAuth instead of the StyledFirebaseAuth, you can just remove the import 'firebaseui/dist/firebaseui.css'; line from the component and you'll be good to go!

Have been looking into this for a bit and this seems to be by far the best fix atm. Turning off Strict Mode is very non-ideal, and I can confirm this works with the latest version of React while leaving it on. Works with Create-React-App (using Vite) as well. Thank you greatly!

@hacker1024
Copy link

A note for anyone experiencing an error like the following when using these solutions:

TypeError: Cannot read properties of undefined (reading 'EmailAuthProvider')

firebase/compat/auth must be imported alongside firebase/compat/app.

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

@JoseVSeb
Copy link

This is fantastic, but I found that redirects (e.g. with Google SSO or Email Link) still do not work correctly in development due to React.StrictMode and the new React 18 "strict effects" feature forcing an unmount and remount after firebaseUiWidget.start(...) transitions from temporary redirect link to the sign-in URL. Couldn't figure out a clean solution here, so went with a hack that skips unmount/remount when redirection is in play.

    const skipStrictEffects = useRef(false);

    useEffect(() => {
        ...
        if (!skipStrictEffects.current) {
            skipStrictEffects.current = firebaseUiWidget.isPendingRedirect();
            firebaseUiWidget.start(elementRef.current, uiConfig);
        }

        return () => {
            if (!skipStrictEffects.current) {
                unregisterAuthObserver();
                firebaseUiWidget.reset();
            }
        };
    }, [firebaseui, uiConfig]);

react 18 strict mode effects on useEffect is meant to tackle fast navigation or something like that. a better solution than above is to use event loop to run code that's affected by the unmount-remount, like so:

  useEffect(() => {
    if (!elementRef.current) return;

    let firebaseUiWidget: auth.AuthUI;
    let unregisterAuthObserver: Unsubscribe;

    // firebase ui start in event loop to solve react 18 strict requirement.
    const timeout = setTimeout(() => {
      // Get or Create a firebaseUI instance.
      firebaseUiWidget =
        auth.AuthUI.getInstance() || new auth.AuthUI(firebaseAuth);
      if (uiConfig.signInFlow === "popup") firebaseUiWidget.reset();

      // We track the auth state to reset firebaseUi if the user signs out.
      unregisterAuthObserver = onAuthStateChanged(firebaseAuth, (user) => {
        if (!user && userSignedIn) firebaseUiWidget.reset();
        setUserSignedIn(!!user);
      });

      // Trigger the callback if any was set.
      if (uiCallback) uiCallback(firebaseUiWidget);

      // Render the firebaseUi Widget.
      firebaseUiWidget.start(elementRef.current as HTMLDivElement, uiConfig);
    });

    return () => {
      clearTimeout(timeout);
      if (unregisterAuthObserver) unregisterAuthObserver();
      if (firebaseUiWidget) firebaseUiWidget.reset();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [uiConfig]);

@keisukekomeda
Copy link

keisukekomeda commented Mar 28, 2024

Thank you for nice library and nice PR.
It seems the change works on my service.

@jhuleatt
I'm looking forward to your approval and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

React 18 StrictMode causes "AuthUI instance is deleted" error