firebase / firebaseui-web-react

React Wrapper for firebaseUI Web
Apache License 2.0
1.26k stars 248 forks source link

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

Open gvillenave opened 2 years ago

gvillenave commented 2 years ago
chuhancheng commented 2 years ago

Hi, is it ready to be merged?

modsushi commented 2 years ago

When is it getting merged?

gvillenave commented 2 years ago

@jhuleatt can you review and merge?

cuongnvn commented 2 years ago

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

aamancio commented 2 years ago

Hey when is this fix getting merged?

gvillenave commented 2 years ago

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

zettry1 commented 2 years ago

waiting for merge

paviln commented 2 years ago

waiting for merge +1

MaurizioTonelli commented 2 years ago

waiting for merge +2

YGKtech commented 2 years ago

I too would greatly appreciate this getting merged

MartinXPN commented 2 years ago

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:

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 commented 2 years ago

waiting for merge +3

lewis-cobry commented 2 years ago

me too!

frontcodelover commented 2 years ago

Please merge it :)

th-m commented 2 years ago

merge 🙏

IAmKio commented 2 years ago

Pretty please with chocolate sprinkles on top? 🙏

Skyblueballykid commented 2 years ago

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

xhonorate commented 2 years ago

waiting for merge +999

gbourne1 commented 2 years ago

@jhuleatt

Can this be merged?

wiktor-cobry commented 2 years ago

Please merge this; no reason not to.

juliankleine commented 2 years ago

Let's go! @jhuleatt Would be really nice if you would find the time, thanks!

juliankleine commented 2 years ago

@jhuleatt If you can't find the time is it possible to dispatch the review to a different maintainer of this project? I think a lot of people would love to see this PR being merged. Thanks for your time!

fsalcedo55 commented 2 years ago

Has anyone found a way of solving this using Next.js?

rubencarv commented 2 years ago

Please merge 🙏

karkir0003 commented 2 years ago

@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 commented 2 years ago

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:

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 commented 2 years ago

Waiting for merge 🙏

michaelangeloio commented 2 years ago

pls merge!

gbourne1 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 1 year ago

can we get this merged please?

cgil commented 1 year ago

@gvillenave Is this ready to be merged?

memark commented 1 year ago

Could we merge this please @jhuleatt?

gbourne1 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

It is a shame that this project is dead

JS-GitRepo commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 5 months ago

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.