Search code examples
javascriptfirebasereact-nativefirebase-authenticationasyncstorage

React Native navigation problem with asyc storage


I have a really strange problem and so far could not identify the possible weak points.

I have a firebase & react-native application with a nested login.

The problem is the navigation. The drawer and the stack navigation works fine, except for the login screen.

When I try to login and the isSignedIn variable becomes true, the screen is not changing for the home screen. It remains the login screen. However, on the console, I see the authentication was successful. If I go back to the code and click a save in the Navigation file (which was posted here), the forced reload seems to solve the problem and I can see the home screen. But this is not automatically.

So confused.

export default function Navigator() {

    const user = firebase.auth().currentUser;
    const [isSignedIn, setIsSignedIn] = useState(false)


    useEffect( () => {
        user ? setIsSignedIn(true) : setIsSignedIn(false)
        if (isSignedIn) storeData(user)
    })

    const storeData = async (value) => {
        try {
          await AsyncStorage.setItem('userObject', JSON.stringify(value))
            alert('Data successfully saved to the storage')
        } catch (e) {
            alert('Failed to save the data to the storage')
        }
      }

    return (
        <NavigationContainer>
        <Stack.Navigator initialRouteName="Login">
            {isSignedIn ? (
            <>
            <Stack.Screen name='loggedInUserNavigationStack' component={loggedInUserNavigationStack} options={{headerShown: false}}>
                </Stack.Screen>   
            </>
            ) : (
            <Stack.Screen name="Log in" component={LoginScreen} options={{headerShown: false}} />
            )}
        </Stack.Navigator>
    </NavigationContainer>
    );
}

function loggedInUserNavigationStack() {
    return (      
        <Drawer.Navigator initialRouteName="mainContentNavigator" drawerContent={ props => <DrawerContent {...props} /> }>
            <Drawer.Screen name="mainContentNavigator" component={mainContentNavigator} options={{ title: 'Home' }} />
            <Drawer.Screen name="About" component={AboutScreen} options={{ title: 'About' }}/>
            <Drawer.Screen name="Archive" component={ArchiveScreen} options={{ title: 'Archive' }}/>
            <Drawer.Screen name="Profile" component={ProfileScreen} options={{ title: 'Profile' }} />
            <Drawer.Screen name="Sign Out" component={SignOutScreen} options={{headerShown: false}}/>
        </Drawer.Navigator>
  );
}

function mainContentNavigator() {
    return (      
        <Stack.Navigator initialRouteName="HomeScreen">
            <Stack.Screen name="Home" component={HomeScreen} options={ 
                ({ navigation }) => {
                    return {
                      headerLeft: () => <Header navigation={navigation} />
                    }
                  }
                    } />
            <Stack.Screen name="CertainHabbit" component={CertainHabbit} options={({ route }) => ({ title: route.params.name })} />
        </Stack.Navigator>
  );
}

Solution

  • Firebase automatically tries to restore the signed-in user when you load the page. But since this requires an async call to the server, it may take some time. Right now, by the time your const user = firebase.auth().currentUser runs, the user hasn't been authenticated yet. And by the time the authentication finishes, your code isn't aware of it.

    The solution is to use an auth state listener, as shown in the first snippet of the documentation on determining the signed in user. For you that'd be something like this:

    useEffect( () => {
      firebase.auth().onAuthStateChanged((user) => {
        setIsSignedIn(!!user);
        if (user) storeData(user)
      });
    })