react navigation infinite re-render when separating navigators files

309 views Asked by At

I want to separate my navigation code into separate files, 3 files to be precise and I want to load a stack navigator depending on the role of the user, which I will receive on the logging in process (like 3 separate apps), the problem is that if I would've kept the file stacked together it'll be very huge file

here is my main navigation file

import * as React from 'react';

// Apps Navigators
import VendorNavigator from '@vendor-app/routes';

// utils
import Api from '@utils/api';
import AsyncStorage from '@react-native-community/async-storage';
import Container from '@components/container';

// Icons
import Ionicons from 'react-native-vector-icons/Ionicons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

// contexts
const AuthContext = React.createContext(null);

// navigators
import {createStackNavigator} from '@react-navigation/stack';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';

// import Screens
// Public Screens
import LoginScreen from '@layouts/auth/login';
import PasswordReset from '@layouts/auth/password-reset';
import PasswordResetCode from '@layouts/auth/password-reset-code';
import NewPassword from '@layouts/auth/new-password';

const Navigator = () => {

  const AuthStack = createStackNavigator();

  const Authenticated = createStackNavigator();

  const UnAuthenticated = createStackNavigator();

  React.useEffect(() => {
    const bootstrapAsync = async () => {
      await verifyUser();
      let token = await AsyncStorage.getItem('access_token');
      if (!token)
        dispatch({ type: 'SIGN_OUT' })
      else
        dispatch({ type: 'SIGN_IN', token: token})
    }
    bootstrapAsync()
  }, [])

  const verifyUser = async () => {
    let response = await Api.get('/auth/verify-token');
    if (response && response.status_code == 200) {
      let token = await AsyncStorage.getItem('access_token');
      dispatch({ type: 'SIGN_IN', token: token })
    } else {
      dispatch({ type: 'SIGN_OUT' })
    }
  }

  const authContext = React.useMemo(
    () => ({
      signIn: async (email_address, password, app) => {
        let response = await Api.post(`auth/login`, {
          email_address: email_address,
          password: password,
        });

        if (response && response.status_code == 200) {
          await AsyncStorage.setItem('access_token', response.data.access_token)
          await AsyncStorage.setItem('refresh_token', response.data.refresh_token)
          await AsyncStorage.setItem('user', JSON.stringify(response.data.user))
          dispatch({ type: 'SIGN_IN', token: response.data.access_token });
        }

        return response;
      },
      signOut: async () => {
        await AsyncStorage.removeItem('access_token')
        await AsyncStorage.removeItem('refresh_token')
        await AsyncStorage.removeItem('user')
        dispatch({ type: 'SIGN_OUT' })
      },
      signUp: async data => {
        dispatch({ type: 'SIGN_IN', token: 'dummy-auth-token' });
      },
    }),
    []
  );

  const [state, dispatch] = React.useReducer(
    (prevState, action) => {
      switch (action.type) {
        case 'SIGN_IN':
          return {
            ...prevState,
            is_authenticated: true,
            token: action.token,
            is_loading: false
          };
        case 'SIGN_OUT':
          return {
            ...prevState,
            is_authenticated: false,
            token: null,
            is_loading: false
          }
      }
    },
    {
      is_loading: true,
      is_authenticated: false,
      token: null
    }
  )

  function AuthenticatedStack() {
    return (
      <Authenticated.Navigator>
        {/*
          The idea is to have a conditional route here depending on the user's role, for example:
          {
            user.role == 'vendor'
            ? <Authenticated.Screen options={{headerShown: false}} name={'Tabs'} component={VendorNavigator} />
            : user.role == 'collector'
            ? <Authenticated.Screen options={{headerShown: false}} name={'Tabs'} component={CollectorNavigator} />
            : null
          }
        */}
        <Authenticated.Screen options={{headerShown: false}} name={'Tabs'} component={VendorNavigator} />
      </Authenticated.Navigator>
    )
  }

  function UnAuthenticatedStack() {
    return (
      <UnAuthenticated.Navigator
        screenOptions={{
          headerShown: false
        }}>
        {/* Login and password resets */}
        <UnAuthenticated.Screen name={'Login'} component={LoginScreen} />
        <UnAuthenticated.Screen name={'PasswordReset'} component={PasswordReset} />
        <UnAuthenticated.Screen name={'PasswordResetCode'} component={PasswordResetCode} />
        <UnAuthenticated.Screen name={'NewPassword'} component={NewPassword} />
      </UnAuthenticated.Navigator>
    )
  }


  return (
    <Container blockRender is_loading={state.is_loading}>
      <AuthContext.Provider value={authContext}>
        <AuthStack.Navigator>
          {
            state.is_authenticated
            ? <AuthStack.Screen options={{headerShown: false}} name={'Home'} component={AuthenticatedStack} />
            : <AuthStack.Screen options={{headerShown: false}} name={'Login'} component={UnAuthenticatedStack} />
          }
        </AuthStack.Navigator>
      </AuthContext.Provider>
    </Container>
  )

}

export {AuthContext};

export default Navigator;

and here is my nested stack

import React from 'react';

// utils
import Api from '@utils/api';
import AsyncStorage from '@react-native-community/async-storage';
import Container from '@components/container';

// Icons
import Ionicons from 'react-native-vector-icons/Ionicons';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';

// navigators
import {createStackNavigator} from '@react-navigation/stack';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';

// import Screens
import ListScreen from '@layouts/list';
import RetailerScreen from '@layouts/list/retailer';
import RetailerInvoiceScreen from '@layouts/list/retailer/invoice';
import CreateInvoiceScreen from '@layouts/list/retailer/create-invoice';
import CreateUserScreen from '@layouts/list/create-user';
import MapScreen from '@layouts/map';
import SettingsScreen from '@layouts/settings';

function VendorNavigator() {

    const tabNavigatorConfig = ({ route }) => ({
    tabBarIcon: ({focused, color, size}) => {
      let iconName;

      if (route.name == 'Retailers')
        iconName = focused ? 'store' : 'store-outline';
      else if (route.name == 'Map')
        iconName = focused ? 'map-sharp' : 'map-outline';
      else if (route.name == 'Settings')
        iconName = focused ? 'settings' : 'settings-sharp';

      return route.name == 'Retailers' ? (
          <MaterialCommunityIcons name={iconName} color={color} size={size} />
        ) : (
          <Ionicons name={iconName} color={color} size={size} />
        )
    }
  })

  const TabNavigator = createBottomTabNavigator();

  const RetailersListStack = createStackNavigator();

  function RetailersScreensAndSubScreens() {
    return (
      <RetailersListStack.Navigator
        screenOptions={{
          headerShown: false
        }}>
        <RetailersListStack.Screen name={'Retailers'} component={ListScreen} />
        <RetailersListStack.Screen name={'Retailer'} component={RetailerScreen} />
        <RetailersListStack.Screen name={'RetailerInvoice'} component={RetailerInvoiceScreen} />
        <RetailersListStack.Screen name={'CreateRetailer'} component={CreateInvoiceScreen} />
        <RetailersListStack.Screen name={'CreateUser'} component={CreateUserScreen} />
      </RetailersListStack.Navigator>
    )
  }

  return (
    <TabNavigator.Navigator
      screenOptions={tabNavigatorConfig}
      tabBarOptions={{
        activeTintColor: '#33A788',
        inactiveTintColor: '#000000',
      }}>
      <TabNavigator.Screen name={'Retailers'} component={RetailersScreensAndSubScreens} />
      <TabNavigator.Screen name={'Map'} component={MapScreen} />
      <TabNavigator.Screen name={'Settings'} component={SettingsScreen} />
    </TabNavigator.Navigator>
  )

}

export default VendorNavigator;

the app loads initially in a perfect way, but as soon as I navigate, it triggers an infinite re-render.

0

There are 0 answers