Fix: React Navigation Not Working — Screens Not Rendering, TypeScript Errors, or Gestures Broken
Quick Answer
How to fix React Navigation issues — stack and tab navigator setup, TypeScript typing, deep linking, screen options, nested navigators, authentication flow, and performance optimization.
The Problem
Screens don’t render after setting up React Navigation:
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
// White screen or crash — no navigation renderedOr gestures (swipe back) don’t work:
Stack screens render but swipe-to-go-back doesn't functionOr TypeScript complains about navigation props:
Property 'navigate' does not exist on type '{}'Why This Happens
React Navigation is the standard navigation library for React Native. It requires several peer dependencies and proper configuration:
- Peer dependencies must all be installed — React Navigation has required dependencies:
react-native-screens,react-native-safe-area-context, andreact-native-gesture-handler. Missing any of these causes crashes or broken features. react-native-gesture-handlermust be imported first — it patches React Native’s gesture system. Import it at the very top of your entry file before anything else.- TypeScript requires explicit navigator typing —
navigation.navigate('Screen')is untyped by default. You must defineRootStackParamListfor type-safe navigation. - Native stack uses native platform navigation —
@react-navigation/native-stackuses iOS UINavigationController and Android Fragment. It’s faster but requiresreact-native-screens. The JS-based@react-navigation/stackdoesn’t need it but is slower.
Fix 1: Complete Setup
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs
npx expo install react-native-screens react-native-safe-area-context react-native-gesture-handler// App.tsx — entry point
import 'react-native-gesture-handler'; // Must be first import
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Ionicons } from '@expo/vector-icons';
// Type definitions for navigation
type RootStackParamList = {
MainTabs: undefined;
Details: { id: string; title: string };
Settings: undefined;
Modal: { message: string };
};
type TabParamList = {
Home: undefined;
Search: { query?: string };
Profile: undefined;
};
const Stack = createNativeStackNavigator<RootStackParamList>();
const Tab = createBottomTabNavigator<TabParamList>();
function MainTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
const icons: Record<string, string> = {
Home: focused ? 'home' : 'home-outline',
Search: focused ? 'search' : 'search-outline',
Profile: focused ? 'person' : 'person-outline',
};
return <Ionicons name={icons[route.name] as any} size={size} color={color} />;
},
tabBarActiveTintColor: '#3b82f6',
tabBarInactiveTintColor: '#9ca3af',
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Search" component={SearchScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
</Tab.Navigator>
);
}
export default function App() {
return (
<SafeAreaProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="MainTabs"
component={MainTabs}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ route }) => ({ title: route.params.title })}
/>
<Stack.Screen name="Settings" component={SettingsScreen} />
<Stack.Screen
name="Modal"
component={ModalScreen}
options={{ presentation: 'modal' }}
/>
</Stack.Navigator>
</NavigationContainer>
</SafeAreaProvider>
);
}Fix 2: Type-Safe Navigation
// types/navigation.ts
import type { NativeStackScreenProps, NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs';
import type { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native';
// Define param lists for each navigator
export type RootStackParamList = {
MainTabs: NavigatorScreenParams<TabParamList>;
Details: { id: string; title: string };
Settings: undefined;
};
export type TabParamList = {
Home: undefined;
Search: { query?: string };
Profile: undefined;
};
// Screen prop types
export type RootStackScreenProps<T extends keyof RootStackParamList> =
NativeStackScreenProps<RootStackParamList, T>;
export type TabScreenProps<T extends keyof TabParamList> =
CompositeScreenProps<
BottomTabScreenProps<TabParamList, T>,
NativeStackScreenProps<RootStackParamList>
>;
// For useNavigation hook
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}// screens/HomeScreen.tsx — typed screen component
import type { TabScreenProps } from '@/types/navigation';
import { View, Text, Button } from 'react-native';
export default function HomeScreen({ navigation }: TabScreenProps<'Home'>) {
return (
<View style={{ flex: 1, padding: 16 }}>
<Text>Home Screen</Text>
<Button
title="Open Details"
onPress={() => navigation.navigate('Details', { id: '123', title: 'My Item' })}
// TypeScript ensures id and title are provided
/>
<Button
title="Open Modal"
onPress={() => navigation.navigate('Modal', { message: 'Hello!' })}
/>
</View>
);
}
// Using useNavigation hook (any component, not just screens)
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
import type { RootStackParamList } from '@/types/navigation';
function SomeButton() {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
return (
<Button
title="Go to Settings"
onPress={() => navigation.navigate('Settings')}
/>
);
}Fix 3: Screen Options and Headers
<Stack.Navigator
screenOptions={{
headerStyle: { backgroundColor: '#ffffff' },
headerTintColor: '#1a1a2e',
headerTitleStyle: { fontWeight: '600' },
headerShadowVisible: false,
animation: 'slide_from_right', // 'default' | 'fade' | 'slide_from_right' | 'slide_from_bottom' | 'none'
}}
>
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ route, navigation }) => ({
title: route.params.title,
headerRight: () => (
<Pressable onPress={() => navigation.navigate('Settings')}>
<Ionicons name="settings-outline" size={24} color="#333" />
</Pressable>
),
// Custom header
// header: (props) => <CustomHeader {...props} />,
// Hide back button text (iOS)
headerBackTitle: '',
// Full screen modal
// presentation: 'fullScreenModal',
})}
/>
</Stack.Navigator>
// Set options dynamically from within a screen
function DetailsScreen({ navigation, route }) {
useLayoutEffect(() => {
navigation.setOptions({
title: `Item ${route.params.id}`,
headerRight: () => <ShareButton />,
});
}, [navigation, route.params.id]);
return <View>...</View>;
}Fix 4: Deep Linking
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
MainTabs: {
screens: {
Home: '',
Search: 'search',
Profile: 'profile',
},
},
Details: 'details/:id',
Settings: 'settings',
},
},
};
<NavigationContainer linking={linking} fallback={<LoadingScreen />}>
<Stack.Navigator>
{/* ... */}
</Stack.Navigator>
</NavigationContainer>
// Links:
// myapp:// → Home tab
// myapp://search → Search tab
// myapp://details/123 → Details screen with id="123"
// https://myapp.com/settings → Settings screenFix 5: Authentication Flow
export default function App() {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) return <SplashScreen />;
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
// Authenticated screens
<>
<Stack.Screen name="MainTabs" component={MainTabs} />
<Stack.Screen name="Details" component={DetailsScreen} options={{ headerShown: true }} />
<Stack.Screen name="Settings" component={SettingsScreen} options={{ headerShown: true }} />
</>
) : (
// Auth screens
<>
<Stack.Screen name="Login" component={LoginScreen} />
<Stack.Screen name="Register" component={RegisterScreen} />
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
// When isAuthenticated changes, React Navigation automatically
// replaces the navigation state — no manual navigation neededFix 6: Performance Optimization
// Lazy load screens
const SettingsScreen = React.lazy(() => import('./screens/SettingsScreen'));
// Or use the lazy prop
<Stack.Screen name="Settings" getComponent={() => require('./screens/SettingsScreen').default} />
// Freeze inactive screens (reduce re-renders)
import { enableFreeze } from 'react-native-screens';
enableFreeze(true); // Call at app startup
// Avoid anonymous functions in screen options
// WRONG — creates new function every render
<Stack.Screen options={() => ({ title: 'Home' })} />
// CORRECT — stable reference
const homeOptions = { title: 'Home' };
<Stack.Screen options={homeOptions} />Still Not Working?
White screen or crash on startup — a peer dependency is missing. Install all required packages: react-native-screens, react-native-safe-area-context, react-native-gesture-handler. For Expo, use npx expo install to get compatible versions.
Gestures (swipe back) don’t work — import 'react-native-gesture-handler' must be the first import in your entry file. If using bare React Native (not Expo), run cd ios && pod install after installing.
TypeScript errors on navigation — define RootParamList in the global ReactNavigation namespace. This enables useNavigation() to be typed across all components without explicit type annotations.
Nested navigator shows double headers — set headerShown: false on the parent screen that contains the nested navigator. Each navigator renders its own header by default.
For related mobile issues, see Fix: Expo Router Not Working and Fix: Expo Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Expo Router Not Working — Routes Not Matching, Layout Nesting Wrong, or Deep Links Failing
How to fix Expo Router issues — file-based routing, layout routes, dynamic segments, tabs and stack navigation, modal routes, authentication flows, and deep linking configuration.
Fix: React Native Paper Not Working — Theme Not Applying, Icons Missing, or Components Unstyled
How to fix React Native Paper issues — PaperProvider setup, Material Design 3 theming, custom color schemes, icon configuration, dark mode, and Expo integration.
Fix: NativeWind Not Working — Styles Not Applying, Dark Mode Broken, or Metro Bundler Errors
How to fix NativeWind issues — Tailwind CSS for React Native setup, Metro bundler configuration, className prop, dark mode, responsive styles, and Expo integration.
Fix: Tamagui Not Working — Styles Not Applying, Compiler Errors, or Web/Native Mismatch
How to fix Tamagui UI kit issues — setup with Expo, theme tokens, styled components, animations, responsive props, media queries, and cross-platform rendering.