Search code examples
react-nativereact-navigationreact-navigation-v5react-navigation-stack

Stack navigator React navigation auth flow using useEffect


I am new to react native and I tried to implement a simple login with navigation between login and home screen using React Navigation. However I cannot figure out how to make user goes to home when previous user creds is stored in asyncStorage and goes to login screen when no creds stored in asyncStorage. I keep getting into login screen even if I'm signed in. I've tried to search, copy, modify any example I found on the internet but none of them meets my need, not to mention every example uses react navigation version 4.xx and below. How can I achieve this, so I can implement them for every other code in my react native project. Maybe I'm missing something or write something incorrectly? Any help appreciated. Also the splash screen setTimeOut seems not working.

The flow should be:

Splash Screen -> isLogin? -> Home

Splash Screen -> is not login? -> login screen

but I keep getting

Splash Screen (in a blink) -> login screen

no matter whether I'm logged in or not

Here is my App.js

import 'react-native-gesture-handler';
import React, {Component, useEffect, useReducer} from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';

import {loadUser} from './src/util/userStorage';
import LoginFunct from './src/loginFunct';
import Home from './src/home';
import SplashScreen from './src/splashScreen';

const Stack = createStackNavigator();

function App({navigation}) {
  const [state, dispatch] = useReducer(
    (prevState, action) => {
      switch (action.type) {
        case 'FETCH_CREDS':
          return {
            ...prevState,
            user: action.user,
            loading: false,
            isLogin: action.isLogin,
          };
      }
    },
    {
      loading: true,
      isLogin: false,
      user: {},
    },
  );

  useEffect(() => {
    const fetchData = () => {
      let userData;
      let loggedIn;
      try {
        // setTimeout(() => {
        loadUser().then((data) => {
          if (data) {
            console.log(data);
            userData = data;
            loggedIn = true;
          }
        });
        // }, 2000);
      } catch (e) {
        console.log(e);
      }
      dispatch({
        type: 'FETCH_CREDS',
        user: userData,
        loading: false,
        isLogin: loggedIn,
      });
    };
    fetchData();
  }, []);

  return (
    <NavigationContainer>
      <Stack.Navigator>
        {!state.loading ? (
          state.isLogin ? (
            <>
            {console.log(state.isLogin)}
            <Stack.Screen
              name="Home"
              component={Home}
              options={{
                title: 'Home',
              }}
            />
            <Stack.Screen
              name="LoginFunct"
              component={LoginFunct}
              options={{
                title: 'Sign In',
              }}
            />
            </>
          ) : (
            <>
            {console.log(state.isLogin)}
            <Stack.Screen
              name="LoginFunct"
              component={LoginFunct}
              options={{
                title: 'Sign In',
              }}
            />
            <Stack.Screen
              name="Home"
              component={Home}
              options={{
                title: 'Home',
              }}
            />
            </>
          )
        ) : (
          <Stack.Screen name="SplashScreen" component={SplashScreen} />
        )}
      </Stack.Navigator>
    </NavigationContainer>
  );
}
export default App;

Here is my userStorage where the loadUser is

import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = 'USER';

export const loadUser = async () => {
  try {
    const data = await AsyncStorage.getItem(STORAGE_KEY);
    if (data !== null) {
      return data;
    }
  } catch (error) {
    console.log('Error user loading', error);
  }
};

export const saveUser = async (data) => {
  try {
    await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(data));
  } catch (error) {
    console.log(error);
  }
};

export const clearUser = async () => {
  try {
    await AsyncStorage.clear();
  } catch (error) {
    console.log(error);
  }
};

Solution

  • Your fetch, and async storage are all asynchronous.

    Try this method instead, by converting it to an async function and using await. This allows your dispatch to be called after the async function is completed.

    useEffect(() => {
    
    const fetchData = async() => {
          let userData;
          let loggedIn;
          try {
            // setTimeout(() => {
            await loadUser().then((data) => {
              if (data) {
                console.log(data);
                userData = data;
                loggedIn = true;
              }
            });
            // }, 2000);
    
          dispatch({
            type: 'FETCH_CREDS',
            user: userData,
            loading: false,
            isLogin: loggedIn,
          });
          } catch (e) {
            console.log(e);
          }
    
        };
          fetchData();
    },[])
    

    Using Async and Await for asynchronous operations

    Alternatively, put your dispatch statement inside the then statement.