mediumReact Native#9

Navigation params typing

Prompt

Define TypeScript types for a React Navigation stack with two screens: Home (no params) and Profile ({ userId: number }). Use the typed navigation hooks.

Solution

type RootStackParamList = {
  Home: undefined
  Profile: { userId: number }
}
type ProfileScreenProps = NativeStackScreenProps<RootStackParamList, 'Profile'>
// Then: const ProfileScreen: React.FC<ProfileScreenProps> = ({ route, navigation }) => {}
// And: const Stack = createNativeStackNavigator<RootStackParamList>()
Mentor's take

The param list type is the single source of truth for every route contract in the app. Once RootStackParamList exists, three things become compile errors instead of runtime crashes: navigating to a screen that doesn't exist, forgetting a required param (navigate('Profile') without userId), and reading a param that was never declared. On a team, this type is the navigation documentation — a new screen isn't done until it's in the param list.

The conventions worth naming: undefined for param-less screens isn't a placeholder — it's what makes navigate('Home') legal with no second argument while navigate('Profile', { userId }) requires one. NativeStackScreenProps<ParamList, 'Profile'> derives both route.params and a navigation prop that knows every valid destination. Passing the generic to createNativeStackNavigator<RootStackParamList>() types the navigator itself, and declaring the list on React Navigation's RootParamList global interface makes bare useNavigation() typed everywhere without per-call generics.

Red flag: typing useNavigation<any> or scattering local type Params = ... next to each screen. Duplicated route contracts drift, and any silently converts every navigation typo into a runtime crash — the exact bug class this type exists to delete.

Say it: "One central param list makes every route and param a compile-time contract — undefined means no params required, and the screens derive their props from it instead of redeclaring them."