Search code examples
javascriptreactjsreact-nativeexporeact-native-tabnavigator

Expo Tab Navigation and Stack Navigation


Hi I am trying to add Tab Navigation when user is logged in and Stack Navigation when User is not logged in. So I have wrote a logic but I don't know why I am getting errors. Kindly help me out.

I wanted to show Tab Navigation only when user has successfully logged in I am using Nodejs backend and Mongodb as database.

This is my AppNavigator file.

AppNavigator.js

import React, { useEffect, useState } from "react";
import { createStackNavigator } from "@react-navigation/stack";
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { NavigationContainer } from '@react-navigation/native';
import SignUp from "../screens/SignupScreen";
import LoginScreen from "../screens/LoginScreen";
import LoadingScreen from "../screens/LoadingScreen";
import HomeScreen from "../screens/HomeScreen";
import AsyncStorage from '@react-native-async-storage/async-storage';
const Stack = createStackNavigator();

const Tab = createBottomTabNavigator()
const [isLoggedin, setLogged] = useState(null)
const TabNavigator = () => {
  return <TabNavigator>
    <Tab.Screen name='Home' component={HomeScreen} />
    <Tab.Screen name='Profile' component={AccountScreen} />
    <Tab.Screen name='Home' component={HomeScreen} />
  </TabNavigator>
}
const AuthNavigator = () => {
  return (
  <Stack.Navigator
    headerMode="none"
    screenOptions={{
      headerStyle: { elevation: 0 },
      cardStyle: { backgroundColor: '#fff' }
    }}
  >


    <Stack.Screen name="loading" component={LoadingScreen}></Stack.Screen>
    <Stack.Screen name="home" component={HomeScreen}></Stack.Screen>

    <Stack.Screen name="login" component={LoginScreen}></Stack.Screen>
    <Stack.Screen name="signup" component={SignUp}></Stack.Screen>


  </Stack.Navigator>
  )
};
const Navigation = () => {
  const [user, setUser] = useState('');
  useEffect(() => {
      if (isLoggedin) {
        setUser(userExist)
      }
      else {
        setUser('')
      }
    
  }, [])
  return (
    <NavigationContainer >
      {user ? <TabNavigator /> : <AuthNavigator />}
    </NavigationContainer>
  )
};
function AppNavigator() {

  useEffect(() => {
    const token = AsyncStorage.getItem('token')
    if (token) {
      setLogged(true)
    }
    else {
      setLogged(false)
    }
  }, [])
  return (
    <Navigation />
  );
}

export default AppNavigator;

This is my App.js file

App.js

import React,{useEffect,useState} from "react";
import { NavigationContainer } from "@react-navigation/native";
import AppNavigator from "./navigation/AppNavigator";

export default function App() {
  return (
      <AppNavigator />

  );
}

I have my own LoadingScreen file

LoadingScreen.js

import { StatusBar } from 'expo-status-bar';
import React,{useEffect,useState} from 'react';
import { StyleSheet, ActivityIndicator, Image,View,Text } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Button, TextInput } from 'react-native-paper';

// <View style={styles.container}>
export default function LoadingScreen(props) {
    
    const detectLogin = () =>{
        const token = AsyncStorage.getItem('token')
        if (token) {
            props.navigation.navigate("profile")
        }
        else {
            props.navigation.navigate("login")
        }
    }
    useEffect(() => {
       detectLogin()
      }, [])
    return (
        <View style={styles.container}>
        <ActivityIndicator size="large" color="black">
         
        </ActivityIndicator>
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        justifyContent: 'center',
    },
  
});


Solution

  • I've created a Snack for you to see the working example

    The first thing that I Noticed is that in your TabNavigator function you wrote

    return <TabNavigator>
        <Tab.Screen name='Home' component={HomeScreen} />
        <Tab.Screen name='Profile' component={AccountScreen} />
        <Tab.Screen name='Home' component={HomeScreen} />
      </TabNavigator>
    

    Instead you have to write something like this

    return <Tab.Navigator> // Notice the . here
        <Tab.Screen name='Home' component={HomeScreen} />
        <Tab.Screen name='Profile' component={AccountScreen} />
        <Tab.Screen name='Home' component={HomeScreen} />
      </Tab.Navigator>
    

    Your token restoration logic is not efficient I guess.

    I would suggest you to make two different Navigators and perform token checking logic inside your App.js

    What you should do is, create a folder called navigation where your App.js is located.

    Then inside navigation create two files AppNavigator.js and AuthNavigator.js

    Your AppNavigator.js should look like this -

    import React from 'react';
    import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
    
    import HomeScreen from '../screens/HomeScreen';
    import AccountScreen from '../screens/AccountScreen';
    
    const Tab = createBottomTabNavigator();
    
    function AppNavigator() {
      return (
        <Tab.Navigator>
          <Tab.Screen name="Home" component={HomeScreen} />
          <Tab.Screen name="Accounts" component={AccountScreen} />
        </Tab.Navigator>
      );
    }
    
    export default AppNavigator;
    

    Your AuthNavigator.js should look like this -

    import React from 'react';
    import { createStackNavigator } from '@react-navigation/stack';
    
    import LoadingScreen from '../screens/LoadingScreen';
    import HomeScreen from '../screens/HomeScreen';
    import LoginScreen from '../screens/LoginScreen';
    import SignUp from '../screens/SignUpScreen';
    
    const Stack = createStackNavigator();
    
    function AuthNavigator() {
      return (
        <Stack.Navigator
          headerMode="none"
          screenOptions={{
            headerStyle: { elevation: 0 },
            cardStyle: { backgroundColor: '#fff' },
          }}>
          <Stack.Screen name="loading" component={LoadingScreen}></Stack.Screen>
          <Stack.Screen name="home" component={HomeScreen}></Stack.Screen>
    
          <Stack.Screen name="login" component={LoginScreen}></Stack.Screen>
          <Stack.Screen name="signup" component={SignUp}></Stack.Screen>
        </Stack.Navigator>
      );
    }
    
    export default AuthNavigator;
    

    Then you should install expo-app-loading from here

    Then in your App.js

    import React, { useEffect, useState } from 'react';
    import { NavigationContainer } from '@react-navigation/native';
    import AppLoading from 'expo-app-loading';
    import AsyncStorage from '@react-native-async-storage/async-storage';
    
    import AppNavigator from './navigation/AppNavigator';
    import AuthNavigator from './navigation/AuthNavigator';
    
    export default function App() {
      const [user, setUser] = useState('');
      const [IsReady, setIsReady] = useState(false);
    
      const RestoreUser = async () => {
        const token = await AsyncStorage.getItem('token');
        if (token) {
          setUser(true);
        } else {
          setUser(false);
        }
      };
    
      if (!IsReady) {
        return (
          <AppLoading
            startAsync={RestoreUser}
            onFinish={() => setIsReady(true)}
            onError={() => {}}
          />
        );
      }
    
      return (
        <NavigationContainer>
          {user ? <AppNavigator /> : <AuthNavigator />}
        </NavigationContainer>
      );
    }
    

    This is the most efficient way of authentication that I use in all my projects..