Home > Back-end >  custom hook returning values first null and then user for React Native and Firebase integration
custom hook returning values first null and then user for React Native and Firebase integration

Time:01-29

I know the reason why this is haapening, but what I want is a solution for this. Code: useAuth.hook.ts

import { useEffect, useState } from "react";
import { getAuth, onAuthStateChanged, User } from "firebase/auth";

const auth = getAuth();

export function useAuth() {
  const [user, setUser] = useState<User>();

  useEffect(() => {
    const unsubscribeFromAuthStateChanged = onAuthStateChanged(auth, (user) => {
      if (user) {
        // User is signed in, see docs for a list of available properties
        // https://firebase.google.com/docs/reference/js/firebase.User
        setUser(user);
      } else {
        // User is signed out
        setUser(undefined);
      }
    });

    return unsubscribeFromAuthStateChanged;
  }, []);

  return user;
}

usage:

 const user = useAuth();

  // return user ? <LandingScreen /> : <AuthStack />;
  if (user) {
    return <UserStack />;
  } else {
    return <AuthStack />;
  }

value returned first time: undefined value returned second time: user from firebase

I know the reson that useEffect renders twice. But is there a solution for this. Because of this in react native app login screen is rendered first and then after few milliseconds main screen.

I looked at multiple answers on Stackoverflow and github issue. I need a workaround this thing so only main screen is rendered.

CodePudding user response:

As your code currently exist, you will always initially render your AuthStack, as the initial value of user is undefined. You could add an additional piece of state to useAuth:

import { useEffect, useState } from "react";
import { getAuth, onAuthStateChanged, User } from "firebase/auth";

const auth = getAuth();

export function useAuth() {
  const [user, setUser] = useState<User>();
  const [isLoading,setIsLoading] = useState(true);

  useEffect(() => {
    const unsubscribeFromAuthStateChanged = onAuthStateChanged(auth, (user) => {
      if (user) {
        // User is signed in, see docs for a list of available properties
        // https://firebase.google.com/docs/reference/js/firebase.User
        setUser(user);
      } else {
        // User is signed out
        setUser(undefined);
      }
      setIsLoading(false);
    });

    return unsubscribeFromAuthStateChanged;
  }, []);

  return {user, isLoading};
}

Usage:

const {user, isLoading} = useAuth();

  // return user ? <LandingScreen /> : <AuthStack />;
  if(isLoading){
    return <Text>Loading...</Text>
  }
  if (user) {
    return <UserStack />;
  } else {
    return <AuthStack />;
  }

While this won't fix the fact that user is initial undefined, it will prevent going to the AuthStack while auth is confirming the user. If you are willing to use different forms of state management you could jotai to store the user. It stores state as atoms independent of components, meaning easy and simple global state. It also have an util function atomWithStorage which reads from storage for the initial value, and rewrites storage on atom updates:

import { useEffect, useState } from "react";
import { getAuth, onAuthStateChanged, User } from "firebase/auth";
import AsyncStorage from "@react-native-async-storage/async-storage";

const auth = getAuth();
// configure atomWithStorage to use AsyncStorage
export const userAtom = atomWithStorage<user>(
  "@firebaseUser",
  undefined,
  {
    getItem: (key) => {
      return AsyncStorage.getItem(key)
        .then((str) => JSON.parse(str))
        .catch((err) => {
          console.log("Error retrieving value:", err);
          return undefined;
        });
    },
    setItem: (key, newValue) => {
      return AsyncStorage.setItem(key, JSON.stringify(user)).catch(
        (err) => {
          console.log("Error storing value:", err);
        }
      );
    },
    removeItem: (key) => AsyncStorage.removeItem(key),
    delayInit: true,
  }
);
export function useAuth() {
  const [user, setUser] = useAtom<User>();
  const [isLoading,setIsLoading] = useState(true);
  const [isAuthorized, setIsAuthorized] = useState(Boolean(user))
  useEffect(() => {
    const unsubscribeFromAuthStateChanged = onAuthStateChanged(auth, (user) => {
      if (user) {
        // User is signed in, see docs for a list of available properties
        // https://firebase.google.com/docs/reference/js/firebase.User
        setUser(user);
      } else {
        // overwriting user here will cause AsyncStorage to overwrite
      }
      setIsLoading(false);
      setIsAuthorized(Boolean(user))
    });

    return unsubscribeFromAuthStateChanged;
  }, []);

  return {user, isLoading, isAuthorized};
}

Usage:

const {user, isLoading, isAuthorized} = useAuth();

  // return user ? <LandingScreen /> : <AuthStack />;
  if(isLoading){
    return <Text>Loading...</Text>
  }
  else if (isAuthorized) {
    return <UserStack />;
  } else {
    return <AuthStack />;
  }
  • Related