How to prevent UI flickering while conditionally rendering components?

Ari*_*aul 7 firebase reactjs firebase-authentication

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
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

sam*_*man 5

这些行:

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

  return () => unsubscribe();
}, []);
Run Code Online (Sandbox Code Playgroud)

可以替换为:

useEffect(() => auth.onAuthStateChanged(setUser), []);
Run Code Online (Sandbox Code Playgroud)

接下来,不要仅传递给nulluseState而是传递当前用户。

const [user, setUser] = useState(null);
Run Code Online (Sandbox Code Playgroud)

变成

const [user, setUser] = useState(auth.currentUser);
Run Code Online (Sandbox Code Playgroud)

这导致:

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

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

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

export default Home
Run Code Online (Sandbox Code Playgroud)

就我个人而言,我倾向于使用undefined// nullusing firebase.auth.User:

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
Run Code Online (Sandbox Code Playgroud)

弹出窗口关闭后,Firebase 身份验证仍然需要处理将提供程序的身份验证令牌交换为 Firebase 用户令牌的身份验证流程。当发生这种情况时,您应该在组件中显示某种形式的加载屏幕。在下面的代码示例中,我将“Continue with Google”文本更改为“Signing in...”,并在登录过程中禁用每个按钮的 onClick 事件。

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;
Run Code Online (Sandbox Code Playgroud)