typescript - expo-router Not Navigating to Route Based on Session State, router.push Fails on Sign Out - Stack Overflow

admin2025-04-26  3

I'm working on a React Native project using expo-router, and I'm having an issue with conditional routing based on the session state. I want to navigate to a login screen (auth.tsx) if there's no active session, or to a tab-based screen ((tabs)/index.tsx) if the user is logged in.

Problem:

  • The app always seems to navigate to the index.tsx file, even though I'm using expo-router with conditional routing based on session state.
  • When I check the session state, it’s correctly logged, but the route isn't behaving as expected.
  • Additionally, when I try to sign out using router.push('app/auth.tsx'), the navigation doesn't work, and it returns an error that the route could not be found.

Code:

    // app/_layout.tsx (this is the root layout)
    import React, { useEffect } from 'react';
    import { View, Text } from 'react-native';
    import { Stack } from 'expo-router';
    import { useAuth } from '../context/AuthContext';

    function AppContent() {
      const { session, isLoading } = useAuth();

      if (isLoading) {
        return (
          <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            <Text>Loading...</Text>
          </View>
        );
      }

      return (
        <Stack screenOptions={{ headerShown: false }}>
          {!session || !session.user ? ( // this does not work
            // Navigate to auth if session is null, undefined, or invalid
            <Stack.Screen name="auth" options={{ headerShown: false }} />
          ) : (
            // Navigate to (tabs) if session and user are valid
            <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          )}
        </Stack>
      );
    }

    export default function App() {
      return (
        <AuthProvider>
          <AppContent />
        </AuthProvider>
      );
    }

I've made sure that the file structure matches expo-router conventions:

    /app
      ├── auth.tsx          // Login screen
      ├── _layout.tsx       // Root layout
      └── (tabs)
          └── index.tsx     // Tab-based screen
          └── _layout.tsx   // Tab-based layout

Sign Out Buttons:

    <TouchableOpacity onPress={async () => {
        try {
            const { error } = await supabase.auth.signOut();
            if (error) {
                    console.error('Error signing out:', error.message);
                } else {
                    console.log('Successfully logged out');
                    // Sign out the user
                    await router.push('../../app/auth.tsx'); 
                 }
             } catch (err) {
                 console.error('Unexpected error during sign out:', err);
                 }
         }}>
             <Text style={[styles.drawerItem, styles.logout]}>Logout</Text>
    </TouchableOpacity>
    <Button
        title="Sign Out"
        onPress={async () => {
            await supabase.auth.signOut(); // Sign out the user
            router.push('/app/Auth.tsx'); // Navigate to the Auth page
        }}
        color="#34d399"
    />

I'm working on a React Native project using expo-router, and I'm having an issue with conditional routing based on the session state. I want to navigate to a login screen (auth.tsx) if there's no active session, or to a tab-based screen ((tabs)/index.tsx) if the user is logged in.

Problem:

  • The app always seems to navigate to the index.tsx file, even though I'm using expo-router with conditional routing based on session state.
  • When I check the session state, it’s correctly logged, but the route isn't behaving as expected.
  • Additionally, when I try to sign out using router.push('app/auth.tsx'), the navigation doesn't work, and it returns an error that the route could not be found.

Code:

    // app/_layout.tsx (this is the root layout)
    import React, { useEffect } from 'react';
    import { View, Text } from 'react-native';
    import { Stack } from 'expo-router';
    import { useAuth } from '../context/AuthContext';

    function AppContent() {
      const { session, isLoading } = useAuth();

      if (isLoading) {
        return (
          <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            <Text>Loading...</Text>
          </View>
        );
      }

      return (
        <Stack screenOptions={{ headerShown: false }}>
          {!session || !session.user ? ( // this does not work
            // Navigate to auth if session is null, undefined, or invalid
            <Stack.Screen name="auth" options={{ headerShown: false }} />
          ) : (
            // Navigate to (tabs) if session and user are valid
            <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          )}
        </Stack>
      );
    }

    export default function App() {
      return (
        <AuthProvider>
          <AppContent />
        </AuthProvider>
      );
    }

I've made sure that the file structure matches expo-router conventions:

    /app
      ├── auth.tsx          // Login screen
      ├── _layout.tsx       // Root layout
      └── (tabs)
          └── index.tsx     // Tab-based screen
          └── _layout.tsx   // Tab-based layout

Sign Out Buttons:

    <TouchableOpacity onPress={async () => {
        try {
            const { error } = await supabase.auth.signOut();
            if (error) {
                    console.error('Error signing out:', error.message);
                } else {
                    console.log('Successfully logged out');
                    // Sign out the user
                    await router.push('../../app/auth.tsx'); 
                 }
             } catch (err) {
                 console.error('Unexpected error during sign out:', err);
                 }
         }}>
             <Text style={[styles.drawerItem, styles.logout]}>Logout</Text>
    </TouchableOpacity>
    <Button
        title="Sign Out"
        onPress={async () => {
            await supabase.auth.signOut(); // Sign out the user
            router.push('/app/Auth.tsx'); // Navigate to the Auth page
        }}
        color="#34d399"
    />
Share Improve this question edited Feb 8 at 20:04 halfer 20.4k19 gold badges109 silver badges202 bronze badges asked Jan 15 at 5:48 VioleteyesVioleteyes 111 bronze badge
Add a comment  | 

3 Answers 3

Reset to default 0
router.push('/app/Auth.tsx'); ===>router.push('/app/auth.tsx');

For your specific case, you can try using the redirect prop (although there could be a better way to achieve this).

In the root layout component you have pasted, try the following (notice the redirect prop for the second screen):

return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="auth" options={{ headerShown: false }} />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} redirect={!session || !session.user} />
    </Stack>
  );

This way, whenever the value of session gets altered and the redirect condition is satisfied (which you have confirmed it does), you will be redirected to the auth route.

I changed auth.tsx -> index.tsx.

This was the only way I could solve the problem. Since React Native uses file-based routing, similar to Next.js where it automatically reads page.jsx/tsx, index.tsx serves as the main entry point. For better practice, it should be paired with _layout.tsx to handle navigation within the folder's scope

app/
  index.tsx        # Main entry point and root stack, handles navigation logic for authenticated vs. unauthenticated users
  _layout.tsx      # Root layout, manages global UI components like headers or footers
  (tabs)/          # Folder for tab-based navigation
    index.tsx      # Entry point for tab navigation, initializes and defines the default tab
    _layout.tsx       # Tab layout, includes shared UI components like a bottom tab bar for pages inside /(tabs)
    otherPages.tsx    # Other pages inside protected /(tabs), accessible after session is validated as not null
转载请注明原文地址:http://anycun.com/QandA/1745597957a90977.html