Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to prevent UI flickering while conditionally rendering components?

Consider the following code:

const Home = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged(authUser => {
      if(authUser) {
        setUser(authUser);
      } else {
        setUser(null)
      }
    });

    return () => unsubscribe();
  }, []);

  return (
    <div>
      {user ? (
        <Hero />
      ) : (
        <Login />
      )}
    </div>
  )
}

export default Home

The Login component has all the functions which handles all the Sign Up, Login and Third-Party Authentications using Firebase.

The problems are:

  1. When I reload the page and if the user is already logged in, it shows the component for some time, and then renders the component, which gives a bad UX.
  2. Also, when I sign in using Google or Facebook, again this component is rendered before finally rendering the component.

Please throw some light into this issue. Your help will be highly appreciated!

Edit: Problem 1 is solved, but problem 2 is not. Here is the relevant code for problem 2:

Login.js

<div style={{ marginBottom: "2%" }}>
    <GoogleSignup />
</div>

GoogleSignup.js

import { GoogleLoginButton } from "react-social-login-buttons";
import firebase from "firebase";
import fire from "../fire";

const GoogleSignup = ({ extensionId }) => {
  const OnSubmitButton = async () => {
    var provider = new firebase.auth.GoogleAuthProvider();

    fire
      .auth()
      .signInWithPopup(provider)
      .then((result) => {
        const credential = result.credential;
        const token = credential.accessToken;
        const user = result.user;
      })
      .catch((error) => {
        console.log(error);
      });
  };

  return (
    <div>
      <GoogleLoginButton
        style={{ fontSize: "17px" }}
        text={"Continue with Google"}
        align={"center"}
        onClick={OnSubmitButton}
      />
    </div>
  );
};

export default GoogleSignup;
like image 317
Aritra Paul Avatar asked May 26 '26 01:05

Aritra Paul


1 Answers

These lines:

useEffect(() => {
  const unsubscribe = auth.onAuthStateChanged(authUser => {
    if(authUser) {
      setUser(authUser);
    } else {
      setUser(null)
    }
  });

  return () => unsubscribe();
}, []);

can be replaced with just:

useEffect(() => auth.onAuthStateChanged(setUser), []);

Next, instead of passing in just null to the useState, pass in current user.

const [user, setUser] = useState(null);

becomes

const [user, setUser] = useState(auth.currentUser);

This results in:

const Home = () => {
  const [user, setUser] = useState(auth.currentUser);

  useEffect(() => auth.onAuthStateChanged(setUser), []);

  return (
    <div>
      {user ? (
        <Hero />
      ) : (
        <Login />
      )}
    </div>
  )
}

export default Home

Personally, I tend to use undefined/null/firebase.auth.User using:

const Home = () => {
  const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
  const loadingUser = user === undefined;

  useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);

  if (loadingUser)
    return null; // or show loading icon, etc.

  return (
    <div>
      {user ? (
        <Hero />
      ) : (
        <Login />
      )}
    </div>
  )
}

export default Home

After the popup has closed, Firebase Authentication still needs to handle the authentication flow of exchanging the provider's authentication token for a Firebase User token. While this is taking place, you should show some form of loading screen in your component. In the below code sample, I change the "Continue with Google" text to "Signing in..." and disable the onClick events for each button while the sign in process takes place.

import { GoogleLoginButton } from "react-social-login-buttons";
import firebase from "firebase";
import fire from "../fire";

const PROVIDER_ID_GOOGLE = firebase.auth.GoogleAuthProvider.PROVIDER_ID;
const ignoreOnClick = () => {};

const GoogleSignup = ({ extensionId }) => {
  const [activeSignInMethod, setActiveSignInMethod] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (activeSignInMethod === null)
      return; // do nothing.

    let disposed = false, provider;

    switch (activeSignInMethod) {
      case PROVIDER_ID_GOOGLE:
        provider = new firebase.auth.GoogleAuthProvider();
        break;
      default:
        // this is here to help catch when you've added a button
        // but forgot to add the provider as a case above
        setError("Unsupported authentication provider");
        return;
    }

    fire.auth()
      .signInWithPopup(provider)
      .then((result) => {
        // const credential = result.credential;
        // const token = credential.accessToken;
        // const user = result.user;

        if (!disposed) {
          setError(null);
          setActiveSignInMethod(null);
        }
      })
      .catch((error) => {
        console.error(`Failed to sign in using ${activeSignInMethod}`, error);
        if (!disposed) {
          setError("Failed to sign in!");
          setActiveSignInMethod(null);
        }
      });

    return () => disposed = true; // <- this is to prevent any "updating destroyed component" errors
  }, [activeSignInMethod]);

  return (
    { error && (<div key="error">{error}</div>) }
    <div key="signin-list">
      <GoogleLoginButton
        style={{ fontSize: "17px" }}
        text={
          activeSignInMethod == PROVIDER_ID_GOOGLE
            ? "Signing in..."
            : "Continue with Google"
        }
        align={"center"}
        onClick={
          activeSignInMethod === null
            ? () => setActiveSignInMethod(PROVIDER_ID_GOOGLE)
            : ignoreOnClick
        }
      />
    </div>
  );
};

export default GoogleSignup;
like image 126
samthecodingman Avatar answered May 28 '26 13:05

samthecodingman



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!