Search code examples
reactjsreact-nativenavigationreact-navigationuse-state

React-Navigation handling screen state


I have a navigation component that looks like this. I want to set the navigation screens based on some state variables (isLoading, isAuthenticated, isOnboarded). However this current setup gives me an error saying:

Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

I understand this is happening because I am setting the state right away as soon as this function is invoked causing a re-render, but I'm not sure how I can change this to implement what I want.

I considered replacing the line

const [screens, setScreens] = useState();

with

let screen;

Then instead of having setScreen() everywhere I would have screen =

However this did not work either since there are times when the states (isLoading, isAuthenticated, isOnboarded) don't satisfy any of the conditions (eg when in transition to signing out) in which case I would want screens to keep its previous state but it is instead set to undefined which throws an error.

const Navigation = () => {
  const { isLoading, isAuthenticated, isOnboarded } = useGlobalContext();

  const [screens, setScreens] = useState();

  // Splash screen while app loading
  if (isLoading)
    setScreens(
      <>
        <Stack.Screen
          name="Splash"
          component={SplashScreen}
        />
      </>
    );
  // Authentication screens while user not authenticated
  else if (!isLoading && !isAuthenticated && isOnboarded == -1)
    setScreens(
      <>
        <Stack.Screen
          name="Register"
          component={RegisterScreen}
        />
        <Stack.Screen
          name="Login"
          component={LoginScreen}
          options={{ animationEnabled: false }}
        />
      </>
    );
  // Onboarding screens while user authenticated but not onboarded
  else if (!isLoading && isAuthenticated && !isOnboarded)
    setScreens(
      <>
        <Stack.Screen
          name="OnboardingBirthday"
          component={OnboardingBirthdayScreen}
        />
        <Stack.Screen
          name="OnboardingGender"
          component={OnboardingGenderScreen}
        />
      </>
    );
  // All other screens while user authenticated and onboarded
  else if (!isLoading && isAuthenticated && isOnboarded === true)
    setScreens(
      <>
        <Stack.Screen name="Home" component={TabNavigator} />
        <Stack.Screen name="Product" component={ProductScreen} />
      </>
    );

  return (
    <NavigationContainer theme={Theme}>
      <Stack.Navigator headerMode="none">{screens}</Stack.Navigator>
    </NavigationContainer>
  );
};

Solution

  • Did you try such approach?

    const Navigation = () => {
      const { isLoading, isAuthenticated, isOnboarded } = useGlobalContext();
    
      const renderSplash = () => (
        <Stack.Screen name="Splash" component={SplashScreen} />
      );
    
      const renderAuth = () => (
        <>
          <Stack.Screen name="Register" component={RegisterScreen} />
          <Stack.Screen
            name="Login"
            component={LoginScreen}
            options={{ animationEnabled: false }}
          />
        </>
      );
    
      const renderOnboarding = () => (
        <>
          <Stack.Screen
            name="OnboardingBirthday"
            component={OnboardingBirthdayScreen}
          />
          <Stack.Screen
            name="OnboardingGender"
            component={OnboardingGenderScreen}
          />
        </>
      );
    
      const renderHome = () => (
        <>
          <Stack.Screen name="Home" component={TabNavigator} />
          <Stack.Screen name="Product" component={ProductScreen} />
        </>
      );
    
      const renderScreens = () => {
        switch (true) {
          case isLoading:
            return renderSplash();
          case !isLoading && !isAuthenticated && isOnboarded == -1:
            return renderAuth();
          case !isLoading && isAuthenticated && !isOnboarded:
            return renderOnboarding();
          case !isLoading && isAuthenticated && isOnboarded:
            return renderHome();
          default:
            throw new Error("No screen to render");
        }
      };
    
      const _appScreens = renderScreens();
    
      return (
        <NavigationContainer theme={Theme}>
          <Stack.Navigator headerMode="none">{_appScreens}</Stack.Navigator>
        </NavigationContainer>
      );
    };
    
    
    
    

    Alternatively, instead of having all functions in one file, you could try to use React component for each of them in separate files.