Authentication Flow
Almost every app needs an authentication flow — show a login screen to unauthenticated users, show the main app to authenticated users, and make it impossible to "go back" to the login screen after signing in. React Navigation handles this with conditional rendering: you define both sets of screens, and render one or the other based on auth state.
The Pattern
Instead of navigating from a Login screen to a Home screen (which would let users press back to get to login), you conditionally render different navigators based on whether the user is signed in:
<Stack.Navigator>
{isSignedIn ? (
<Stack.Screen name="Home" component={HomeScreen} />
) : (
<Stack.Screen name="Login" component={LoginScreen} />
)}
</Stack.Navigator>
When isSignedIn changes from false to true, React Navigation replaces the Login screen with the Home screen. There's no transition to go "back" to — the Login screen is simply gone from the navigator.
Auth Context
Manage auth state with React Context so any screen can access the sign-in/sign-out functions:
import React from 'react';
const AuthContext = React.createContext(null);
function AuthProvider({ children }) {
const [isSignedIn, setIsSignedIn] = React.useState(false);
const authContext = React.useMemo(() => ({
isSignedIn,
signIn: () => setIsSignedIn(true),
signOut: () => setIsSignedIn(false),
}), [isSignedIn]);
return (
<AuthContext.Provider value={authContext}>
{children}
</AuthContext.Provider>
);
}
function useAuth() {
const context = React.useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
Login Screen
The login screen calls signIn when the user authenticates:
function LoginScreen() {
const { signIn } = useAuth();
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
<Text style={styles.subtitle}>Please sign in to continue</Text>
<Button title="Sign In" onPress={signIn} />
</View>
);
}
Main App Screens
The main app includes a sign-out button:
function HomeScreen() {
const { signOut } = useAuth();
return (
<View style={styles.container}>
<Text style={styles.title}>Home</Text>
<Button title="Sign Out" onPress={signOut} />
</View>
);
}
Complete Example
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button, StyleSheet } from 'react-native';
const Stack = createNativeStackNavigator();
const AuthContext = React.createContext(null);
function useAuth() {
const context = React.useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
}
function LoginScreen() {
const { signIn } = useAuth();
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
<Text style={styles.subtitle}>Please sign in to continue</Text>
<Button title="Sign In" onPress={signIn} />
</View>
);
}
function SignUpScreen() {
const { signIn } = useAuth();
return (
<View style={styles.container}>
<Text style={styles.title}>Create Account</Text>
<Button title="Sign Up" onPress={signIn} />
</View>
);
}
function HomeScreen({ navigation }) {
const { signOut } = useAuth();
return (
<View style={styles.container}>
<Text style={styles.title}>Home</Text>
<Button title="Profile" onPress={() => navigation.navigate('Profile')} />
<Button title="Sign Out" onPress={signOut} />
</View>
);
}
function ProfileScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Profile</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
title: { fontSize: 24, marginBottom: 8 },
subtitle: { fontSize: 16, color: '#666', marginBottom: 24 },
});
export default function App() {
const [isSignedIn, setIsSignedIn] = React.useState(false);
const authContext = React.useMemo(() => ({
isSignedIn,
signIn: () => setIsSignedIn(true),
signOut: () => setIsSignedIn(false),
}), [isSignedIn]);
return (
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<Stack.Navigator>
{isSignedIn ? (
<>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</>
) : (
<>
<Stack.Screen name="Login" component={LoginScreen} options={{ headerShown: false }} />
<Stack.Screen name="SignUp" component={SignUpScreen} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
</AuthContext.Provider>
);
}
When isSignedIn flips to true, the Login and SignUp screens are removed from the navigator and replaced with Home and Profile. The user sees a clean transition to the Home screen with no way to navigate back to login. Signing out flips the state back, removing the app screens and showing Login.
This pattern scales to real apps — just replace the signIn function with actual API calls and token storage (like expo-secure-store). In the next lesson we'll look at presenting screens as modals.