Skip to content

Fix: React Navigation Not Working — Screens Not Rendering, TypeScript Errors, or Gestures Broken

FixDevs ·

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 rendered

Or gestures (swipe back) don’t work:

Stack screens render but swipe-to-go-back doesn't function

Or 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, and react-native-gesture-handler. Missing any of these causes crashes or broken features.
  • react-native-gesture-handler must 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 typingnavigation.navigate('Screen') is untyped by default. You must define RootStackParamList for type-safe navigation.
  • Native stack uses native platform navigation@react-navigation/native-stack uses iOS UINavigationController and Android Fragment. It’s faster but requires react-native-screens. The JS-based @react-navigation/stack doesn’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 screen

Fix 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 needed

Fix 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 workimport '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.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles