React Navigation v6 with TypeScript

Setting up your React Native mobile application navigation with TypeScript is as simple as it can be. Understanding it only takes two steps.

If you understand the two steps involved, then you'd be able to structure any navigation for your react native app with full type checks.

  1. Type checking the navigator.
  2. Type checking the individual screens.

Let's create a native stack navigation structure with 3 screens: Home, Settings and Profile Screens.

1. Type Checking the Navigator

The first thing you need to do is to create an object type with mappings for route name to the params of the route. In our case, our Screens don't depend on any params, so we'd map the route name to undefined.

// I call this object type_ _RootStackParamList
export type RootStackParamList = {
  Home: undefined;
  Profile: undefined;
  Settings: undefined;
};

Now when you create the navigator with createXNavigator (X can be Stack, BottomTab, Drawer), you have to let it know of the structure you created above.

//Instead of this
//const Stack = createNativeStackNavigator();

// Do this
const Stack = createNativeStackNavigator<RootStackParamList>();

Now we can create our Navigator as follows:

<Stack.Navigator initialRouteName="Home">
  <Stack.Screen name="Home" component={() => <View />} />
  <Stack.Screen name="Profile" component={() => <View />} />
  <Stack.Screen name="Settings" component={() => <View />} />
</Stack.Navigator>

The code above is fully type-checked, the name props of the screens can only be one of the names defined in your RootStackParamList. **Enjoying the benefits of TypeScript already 😇.

This will provide type checking and IntelliSense for props of the Navigator and Screen components. The initialRouteNamepropof the Navigator can only be one of the keys(Home, Profile, Settings) defined in the *RootStackParamList *object.

Now to the second step.

2. Type Checking Individual Screens

Why do we need to type check the individual screens, it's because we need to annotate the navigation prop and the route prop received by a screen. We want IntelliSense when we want to navigate to another screen, we don't want to keep going to the Navigator to check the name of our screens, we want TypeScript to show us the valid/available names.

// Every react-navigation screen receives route and navigation props
function ProfileScreen({ route, navigation }) {
  // ...
}

The navigator packages in React Navigation export a generic type to define types for both the navigation and route props from the corresponding navigator.

For example, you can use NativeStackScreenProps for the Native Stack Navigator (@react-navigation/native), StackScreenProps for Stack Navigator (@react-navigation/stack), DrawerScreenProps for Drawer Navigator (@react-navigation/drawer ), BottomTabScreenProps for Bottom Tab Navigator (@react-navigation/bottom-tabs) and so on.

For our case, we'd use NativeStackScreenProps for the Native Stack Navigator.

This type takes 3 generics:

  1. The param list object is defined for the navigator (RootStackParamList)
  2. The name of the route to which the current screen belongs.
  3. The ID of the navigator (this is optional)

For example:

type Props = NativeStackScreenProps<RootStackParamList, "Profile", "MyStack">;

image

Create Params for the Navigator and Create Props for the Screen

Now let's apply this to our Profile Screen.

type ProfileProps = NativeStackScreenProps<RootStackParamList, "Profile">;
// Every react-navigation screen receives route and navigation props
function ProfileScreen({ route, navigation }: ProfileProps) {
  // ...
}

This now allows us to type check route names and params which you're navigating using navigatepush etc. TypeScript now knows which screens are possible to navigate to, from the Profile Screen as seen below.

image

Intellisense on the navigate() function

For a scenario where our Settings Screen requires the userID as a parameter, our param list would be like this:

export type RootStackParamList = {
  Home: undefined;
  Profile: undefined;
  Settings: {
    userId: number;
  };
};

Now TypeScript highlights the navigation.navigate('Settings') as seen below:

image

TypeScript complaining about the Settings route

This is because we defined the Settings route to accept the userID parameter in the params list. This is exactly why we want our navigation to be type-checked by TypeScript.

image

What is needed?

Hovering over navigate shows what exactly is expected as seen above.

image

You can see the error has disappeared.

Now the error is gone after passing the params object withuserID as the second parameter in the navigate function.

Enjoy structuring your React Navigation with proper type-check and intellisense provided by TypeScript.

As recommended by React Navigation, you can extract all the types to a separate file depending on your project.

types.ts

import {
  createNativeStackNavigator,
  NativeStackScreenProps,
} from "@react-navigation/native-stack";
import { Button, View } from "react-native";

export type RootStackParamList = {
  Home: undefined;
  Profile: undefined;
  Settings: {
    userId: number;
  };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

<Stack.Navigator initialRouteName="Home">
  <Stack.Screen name="Home" component={() => <View />} />
  <Stack.Screen name="Profile" component={ProfileScreen} />
  <Stack.Screen name="Settings" component={() => <View />} />
</Stack.Navigator>;

type ProfileProps = NativeStackScreenProps<RootStackParamList, "Profile">;

// Every react-navigation screen receives route and navigation props
function ProfileScreen({ route, navigation }: ProfileProps) {
  return (
    <View>
      <Button
        title="Go to Settings"
        onPress={() => {
          navigation.navigate("Settings", {
            userId: 4,
          });
        }}
      />
    </View>
  );
}

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics