Type a React Navigation Stack Navigator with Param Lists and Header Options
Define typed navigation params, set global header screenOptions, and override headers per screen in React Navigation for React Native.
11 Jan 2026, 23:29 UTC

Problem and takeaway
If your React Native app passes navigation parameters without types, a misspelled route name or a missing userId only appears at runtime. React Navigation addresses this with TypeScript param lists, while screenOptions lets you configure headers globally or per screen. This guide shows how to type a stack navigator, set header defaults, override them on individual screens, and verify the result.
What the pieces do
NavigationContainer is the root component that stores the navigation state tree. createStackNavigator builds a stack navigator, which manages screen history similar to a call stack: pushing a screen adds it on top, and going back removes it. screenOptions is an options object applied to every screen inside a navigator. useRoute is a hook that returns the current route object, including its parameters. A value is serializable if it can be converted to JSON and reconstructed, such as strings, numbers, booleans, and plain objects or arrays containing those types.
Worked configuration
This example assumes React Navigation 5.x or later. Check your installed version before copying the code, because version 4 uses a different API.
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { useNavigation, useRoute } from '@react-navigation/native';
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Settings: undefined;
};
const Stack = createStackNavigator<RootStackParamList>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator
screenOptions={
headerShown: true,
headerTitle: 'My App',
headerBackTitleVisible: false,
}
>
<Stack.Screen name='Home' component={Home} />
<Stack.Screen
name='Profile'
component={Profile}
options={{ headerTitle: 'Profile' }}
/>
<Stack.Screen name='Settings' component={Settings} />
</Stack.Navigator>
</NavigationContainer>
);
}
function Home() {
const navigation = useNavigation();
return (
<Button
title='Open profile'
onPress={() => navigation.navigate('Profile', { userId: '123' })}
/>
);
}
function Profile() {
const route = useRoute<RootStackParamList['Profile']>();
return <Text>User {route.params.userId}</Text>;
}
The RootStackParamList interface maps each route name to its parameter type or undefined when the screen accepts no parameters. Passing that interface to createStackNavigator<RootStackParamList>() types the navigation and route props for every screen in the stack.
The screenOptions prop on Stack.Navigator sets headerShown and headerTitle for all three screens. The options prop on the Profile Stack.Screen overrides the title for that screen only. In Profile, useRoute<RootStackParamList['Profile']>() makes route.params.userId a string instead of any.
Commands and checks
Run these in your project root with normal user permissions; no sudo is required for local dependencies.
- Check installed version:
npm list @react-navigation/native. Expected output shows the installed version. Confirm it matches the peer dependency range of@react-navigation/stack. Mismatched versions are a common cause of silent failures. - Type-check the project:
npx tsc --noEmit. Expected result is no type errors related to navigation. Ifnavigation.navigate('Profile', { userId: 123 })produces an error, that is TypeScript catching a mismatch between the passed value and the param list. - Runtime check: Open the app, navigate from Home to Profile, and confirm the header reads Profile. Add
console.log(route.params)in Profile and confirm the object survives a hot reload.
Risk: Do not pass functions, class instances, or non-plain objects in navigation params. React Navigation serializes params for state saving and deep-link restoration; non-serializable values break that process.
Limits and common mistakes
- Nested navigators:
screenOptionson a stack navigator does not automatically affect screens inside a nested tab or drawer navigator. Scope options to each navigator separately. - Missing wrapper: Omitting
NavigationContainercauses navigators to throw or fail silently. - Undefined params: A screen typed as
undefinedcannot receive parameters. Passing params to it is a type error. - Header not updating: If the per-screen
optionsprop is ignored, the screen may be rendered inside another navigator whose ownscreenOptionstakes precedence. - In-app only: React Navigation handles navigation inside the app. Native deep links and universal links still require platform-specific Android and iOS configuration.
Practical verification
Wrap your root component in NavigationContainer, add a stack with Home and Profile, and confirm the header appears. Navigate to Profile with { userId: '123' }, log route.params, trigger a hot reload, and confirm the value is still present. If types break compilation, revert the file and compare your param list against the route names you actually use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.