React Navigation is the standard navigation library for React Native, but the setup involves a lot of boilerplate: typed navigation props, stack/tab/drawer composition, deep link configuration, and authentication flows. Claude Code generates the complete navigation structure including TypeScript types, deep link handlers, and the conditional navigation pattern for authenticated vs unauthenticated users.
Navigation Setup
Set up React Navigation for our app.
Structure: bottom tabs (Home, Search, Profile) with a stack inside each.
Auth flow: if not logged in, show Login/Register screens instead of tabs.
Type Definitions
// src/navigation/types.ts
import { NavigatorScreenParams } from '@react-navigation/native';
// Each stack gets its own param list
export type HomeStackParamList = {
HomeScreen: undefined;
ProductDetail: { productId: string; title: string };
OrderHistory: undefined;
};
export type SearchStackParamList = {
SearchScreen: undefined;
SearchResults: { query: string };
ProductDetail: { productId: string; title: string };
};
export type ProfileStackParamList = {
ProfileScreen: undefined;
EditProfile: undefined;
Settings: undefined;
NotificationPreferences: undefined;
};
export type TabParamList = {
Home: NavigatorScreenParams<HomeStackParamList>;
Search: NavigatorScreenParams<SearchStackParamList>;
Profile: NavigatorScreenParams<ProfileStackParamList>;
};
export type AuthStackParamList = {
Login: { returnTo?: string };
Register: undefined;
ForgotPassword: { email?: string };
};
export type RootStackParamList = {
Main: NavigatorScreenParams<TabParamList>;
Auth: NavigatorScreenParams<AuthStackParamList>;
Modal: { screen: string; params?: object }; // Full-screen modals
NotFound: undefined;
};
Root Navigator
// src/navigation/RootNavigator.tsx
import { NavigationContainer, DefaultTheme } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { useAuthStore } from '@/stores/auth';
import { TabNavigator } from './TabNavigator';
import { AuthNavigator } from './AuthNavigator';
import { linking } from './linking';
import { LoadingScreen } from '@/screens/LoadingScreen';
const Stack = createNativeStackNavigator<RootStackParamList>();
export function RootNavigator() {
const { isAuthenticated, isHydrated } = useAuthStore();
// Don't render navigation until auth state is loaded from storage
if (!isHydrated) {
return <LoadingScreen />;
}
return (
<NavigationContainer linking={linking} theme={navigationTheme}>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
// Authenticated routes
<>
<Stack.Screen name="Main" component={TabNavigator} />
<Stack.Screen
name="Modal"
component={ModalScreen}
options={{ presentation: 'modal' }}
/>
</>
) : (
// Auth routes — separate stack prevents going "back" into auth
<Stack.Screen name="Auth" component={AuthNavigator} />
)}
</Stack.Navigator>
</NavigationContainer>
);
}
Tab Navigator
// src/navigation/TabNavigator.tsx
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';
const Tab = createBottomTabNavigator<TabParamList>();
export function TabNavigator() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
tabBarIcon: ({ focused, color, size }) => {
const icons: Record<keyof TabParamList, { focused: string; unfocused: string }> = {
Home: { focused: 'home', unfocused: 'home-outline' },
Search: { focused: 'search', unfocused: 'search-outline' },
Profile: { focused: 'person', unfocused: 'person-outline' },
};
const icon = icons[route.name];
return <Ionicons name={focused ? icon.focused : icon.unfocused} size={size} color={color} />;
},
tabBarActiveTintColor: '#2563eb',
tabBarInactiveTintColor: '#9ca3af',
})}
>
<Tab.Screen name="Home" component={HomeStack} />
<Tab.Screen name="Search" component={SearchStack} />
<Tab.Screen name="Profile" component={ProfileStack} />
</Tab.Navigator>
);
}
Typed Navigation Hooks
// src/navigation/hooks.ts
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
// Typed hook for each navigator
export function useHomeNavigation() {
return useNavigation<NativeStackNavigationProp<HomeStackParamList>>();
}
export function useProductDetailRoute() {
return useRoute<RouteProp<HomeStackParamList, 'ProductDetail'>>();
}
// Usage in a component — fully typed params
function ProductDetailScreen() {
const { params } = useProductDetailRoute();
const navigation = useHomeNavigation();
// params.productId and params.title are typed
// navigation.navigate('OrderHistory') — type-checked navigation target
return <Text>{params.title}</Text>;
}
Deep Linking
Configure deep links so that:
- myapp://product/123 opens the product detail
- https://myapp.com/orders/456 opens order history with the order selected
- SMS link from auth email opens the ForgotPassword screen with email prefilled
// src/navigation/linking.ts
import { LinkingOptions } from '@react-navigation/native';
import { RootStackParamList } from './types';
export const linking: LinkingOptions<RootStackParamList> = {
prefixes: [
'myapp://',
'https://myapp.com',
'https://www.myapp.com',
],
config: {
screens: {
Main: {
screens: {
Home: {
screens: {
ProductDetail: 'product/:productId',
OrderHistory: 'orders',
},
},
Profile: {
screens: {
Settings: 'settings',
},
},
},
},
Auth: {
screens: {
ForgotPassword: {
path: 'reset-password',
parse: { email: (email: string) => decodeURIComponent(email) },
},
},
},
},
},
// Custom URL handler for complex cases
getStateFromPath: (path, options) => {
// Handle /product/123 → navigate to Main/Home/ProductDetail
const productMatch = path.match(/^\/product\/(.+)/);
if (productMatch) {
return {
routes: [{
name: 'Main',
state: {
routes: [{
name: 'Home',
state: {
routes: [
{ name: 'HomeScreen' },
{ name: 'ProductDetail', params: { productId: productMatch[1] } },
],
},
}],
},
}],
};
}
// Fall back to default parsing
return getStateFromPath(path, options);
},
};
Authentication Flow with Persistent Login
When the app opens, check for a stored token.
If valid, go directly to the app. If expired, show login.
After login, navigate to where the user was trying to go.
// src/stores/auth.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { jwtDecode } from 'jwt-decode';
interface AuthState {
token: string | null;
user: User | null;
isHydrated: boolean;
isAuthenticated: boolean;
login: (token: string, user: User) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set, get) => ({
token: null,
user: null,
isHydrated: false,
get isAuthenticated() {
const { token } = get();
if (!token) return false;
try {
const decoded = jwtDecode<{ exp: number }>(token);
return decoded.exp * 1000 > Date.now(); // Not expired
} catch {
return false;
}
},
login: (token, user) => set({ token, user }),
logout: () => set({ token: null, user: null }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
onRehydrateStorage: () => (state) => {
if (state) state.isHydrated = true;
},
},
),
);
Navigating After Authentication
// After successful login, navigate to where user was trying to go
function LoginScreen() {
const navigation = useNavigation<NativeStackNavigationProp<AuthStackParamList>>();
const route = useRoute<RouteProp<AuthStackParamList, 'Login'>>();
const login = useAuthStore(s => s.login);
async function handleLogin(email: string, password: string) {
const { token, user } = await authService.login(email, password);
login(token, user);
// Navigation state updates automatically via RootNavigator's isAuthenticated check
// But if there was a returnTo, navigate there after auth stack unmounts
// (handled by RootNavigator — the conditional rendering resets navigation)
}
// ...
}
The conditional isAuthenticated check in RootNavigator means navigation resets automatically when auth state changes — no manual navigation needed after login/logout.
For the complete React Native setup including Expo configuration and native module linking, see the React Native guide. For testing React Native navigation flows with Jest and React Native Testing Library, see the testing guide. The Claude Skills 360 bundle includes mobile navigation skill sets for complex flows including auth, deep links, and tab/stack composition. Start with the free tier to try navigation scaffold generation.