React Navigation auth flow. Auth navigator is visible before Main navigator for short period of time

Viewed 96

I am using firebase authentication for user authentication and react-navigation to render different navigators based on user state. In Firebase.js I listen for auth state change and set state accordingly. The problem is, when I set user data and hide splash screen (npm package react-native-splash-screen) Auth stack can still be seen before Main stack is rendered. I want to ask a question, how could this be solved?

Firebase.js context provider

class Firebase extends React.Component {
    constructor(props: Props) {
        super(props);
        this.state = {
            user: null,
            userDoc: null,
            isLoading: true,
        };
    }
    usersRef = firestore().collection('Users');
    componentDidMount() {
        const { isLoading, user } = this.state;
        auth().onAuthStateChanged(res => {
            if (res) {
                this.usersRef?.doc(res.uid).onSnapshot(async snapshot => {
                    this.setState(
                        {
                            user: res,
                            userDoc: snapshot.exists ? snapshot.data() : null,
                            isLoading:false
                        },
                        () => SplashScreen.hide();
                    );
                });
            } else {
                this.setState(
                    {
                        user: null,
                        isUserSubscribed: false,
                        isLoading: false,
                    },
                    () => SplashScreen.hide();
                );
            }
        });
    }
}

RootNavigator.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import MainNavigatorWrapper from '../MainNavigator';
import AuthNavigator from '../AuthNavigator';
import { useFirebase } from '../../context';

const Stack = createNativeStackNavigator();

const RootNavigator = () => {
    const { user } = useFirebase();
    return (
        <NavigationContainer>
            <Stack.Navigator}>
                {!user ? (
                    <Stack.Screen name="Auth" component={AuthNavigator} />
                ) : (
                    <Stack.Screen name="Main" component={MainNavigatorWrapper} />
                )}
            </Stack.Navigator>
        </NavigationContainer>
    );
};
1 Answers

I'm posting this because I was looking for a solution to the same problem. I came up with a hack. I'm sure there is a better way to fix this issue but I haven't figured it out yet. This is what I did.

In App.js I return this:

export default function App() {

    const [dataLoaded, setDataLoaded] = useState(false)

    useEffect(() => {
        async function loadApp() {
            try {
                await SplashScreen.preventAutoHideAsync()
                // DO APP LOADING STUFF HERE
            } catch (err) {
                console.warn(err)
            } finally {
                setDataLoaded(true)
            }
        }
        loadApp()
    }, [])

    if (!dataLoaded){
        return null
    }
 
   return (
      <AuthenticationContextProvider>
         <Navigation />
      </AuthenticationContextProvider>
   )
}

As you can see, I'm using expo's SplashScreen API. So, the trick was to hide everything behind the Splash Screen until the authentication was resolved.

My Navigation component looks like this:

    export const Navigation = () => {
   const { isAuthenticated } = useContext(AuthenticationContext)

    const splashHiddenRef = useRef(false)

    useEffect(() => {
        // splash screen hasn't been hidden yet and authentication has already been determined
        if (!splashHiddenRef.current && isAuthenticated !== null) {
            // delay the splash screen from hiding to quick. Otherwise, the AuthenticationNavigator
            // will flash before the AppNavigator is shown (when user is already authenticated on load)
            setTimeout(() => {
                hideAsync()
            }, 1000)
        }
    }, [isAuthenticated])

   return <NavigationContainer>{isAuthenticated ? <AppNavigator /> : <AccountNavigator />}</NavigationContainer>
}

As you can see, I delay the hiding of the Splash Screen for 1 second. If there was a slow network connection this may not work. I'm not certain. For now, it's kind of a hack that I'm messing with.

What I tried prior to this hack:

It didn't work before, but isAuthenticated in my context has an initial value of null. When the app loads, once the authentication is resolved, this value becomes either true or false (never to be null again). So, I assumed once the auth was NOT null then it should load the correct navigator without the flicker of the auth navigator. I believe it has something to do with the initial rendering of the layout. The SpashScreen API solves this issues like this:

Expo SplashScreen

The problem is this isn't a very good example. To my knowledge, you can't use onLayout in a navigator. I think you can only use it in subclasses of React.View.

Perhaps someone can provide a better solution.....this worked for me.....for now

Related