From 06bd61fc7a7f01952db528364d79eff92ecb3cfe Mon Sep 17 00:00:00 2001 From: Lucas Lois Date: Fri, 4 Aug 2023 21:38:16 -0300 Subject: [PATCH] feat: makes NavigationContainerRef.getCurrentRoute type safe --- example/__typechecks__/common.check.tsx | 34 +++++++++++++++++++++++++ example/__typechecks__/static.check.tsx | 26 +++++++++++++++++++ packages/core/src/types.tsx | 14 +++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/example/__typechecks__/common.check.tsx b/example/__typechecks__/common.check.tsx index 85fd35168b..fa640cf2e4 100644 --- a/example/__typechecks__/common.check.tsx +++ b/example/__typechecks__/common.check.tsx @@ -8,8 +8,10 @@ import type { } from '@react-navigation/drawer'; import type { CompositeScreenProps, + NavigationContainerRef, NavigationHelpers, NavigatorScreenParams, + Route, } from '@react-navigation/native'; import type { StackNavigationOptions } from '@react-navigation/stack'; import { @@ -350,3 +352,35 @@ export const ThirdScreen = ({ // @ts-expect-error if (ScreenName === 'NoParams') navigation.navigate(ScreenName, { id: '123' }); }; + +/** + * Check for errors on getCurrentRoute + */ +declare const navigationRef: NavigationContainerRef; +const route = navigationRef.getCurrentRoute()!; + +switch (route.name) { + case 'PostDetails': + expectTypeOf(route.params).toMatchTypeOf<{ + id: string; + section?: string; + }>(); + break; + case 'Login': + expectTypeOf(route.params).toMatchTypeOf(); + break; + case 'Account': + expectTypeOf(route.params).toMatchTypeOf(); + break; + case 'Popular': + expectTypeOf(route.params).toMatchTypeOf<{ + filter: 'day' | 'week' | 'month'; + }>(); + break; +} + +declare const navigationRefUntyped: NavigationContainerRef; + +expectTypeOf(navigationRefUntyped.getCurrentRoute()).toMatchTypeOf< + Route | undefined +>(); diff --git a/example/__typechecks__/static.check.tsx b/example/__typechecks__/static.check.tsx index 83019ef7c4..7a303b3278 100644 --- a/example/__typechecks__/static.check.tsx +++ b/example/__typechecks__/static.check.tsx @@ -1,5 +1,6 @@ import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import type { + NavigationContainerRef, NavigationProp, StaticParamList, StaticScreenProps, @@ -196,3 +197,28 @@ createStackNavigator({}); createStackNavigator({ screens: {}, }); + +/** + * Check for errors on getCurrentRoute + */ +declare const navigationRef: NavigationContainerRef; +const route = navigationRef.getCurrentRoute()!; + +switch (route.name) { + case 'Profile': + expectTypeOf(route.params).toMatchTypeOf<{ + user: string; + }>(); + break; + case 'Settings': + expectTypeOf(route.params).toMatchTypeOf(); + break; + case 'Login': + expectTypeOf(route.params).toMatchTypeOf(); + break; + case 'Register': + expectTypeOf(route.params).toMatchTypeOf<{ + method: 'email' | 'social'; + }>(); + break; +} diff --git a/packages/core/src/types.tsx b/packages/core/src/types.tsx index f2562c9e8c..21c8ec3be3 100644 --- a/packages/core/src/types.tsx +++ b/packages/core/src/types.tsx @@ -648,6 +648,18 @@ export type NavigationContainerEventMap = { }; }; +type ParamListRoute = { + [RouteName in keyof ParamList]: ParamList[RouteName] extends NavigatorScreenParams< + infer T extends ParamListBase + > + ? ParamListRoute + : Route, ParamList[RouteName]>; +}[keyof ParamList]; + +type MaybeParamListRoute = ParamList extends ParamListBase + ? ParamListRoute + : Route; + export type NavigationContainerRef = NavigationHelpers & EventConsumer & { @@ -664,7 +676,7 @@ export type NavigationContainerRef = /** * Get the currently focused navigation route. */ - getCurrentRoute(): Route | undefined; + getCurrentRoute(): MaybeParamListRoute | undefined; /** * Get the currently focused route's options. */