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:
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:
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"
/>
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