Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: makes NavigationContainerRef.getCurrentRoute type safe #11525

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

lucasloisp
Copy link

Motivation

When doing analytics, it has been quite helpful to know the current screen and its params on the onStateChange callback directly, instead of deriving that from state by running through nested routes.

Even then, there are other cases that use getCurrentRoute and could use a more specific type than Route<string> based on the already present ParamList generic on NavigationContainerRef.

Currently, the typed returned is Route<string>, which provides no info on the valid "names" (as defined on the ParamList), nor on the params, whose type could be restricted by checking for name.

Test plan

All changes presented are typescript-only, validated with tests on example/__typechecks__ for both the cases (static or otherwise) with a defined ParamList as well as an empty type {} returning Route<string> which could be useful for projects adopting TS but that haven't yet typed their routes.

@github-actions
Copy link

github-actions bot commented Aug 5, 2023

Hey @lucasloisp! Thanks for opening your first pull request in this repo. If you haven't already, make sure to read our contribution guidelines.

@codecov-commenter
Copy link

codecov-commenter commented Aug 5, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Comparison is base (8e7ac4f) 75.96% compared to head (72eea65) 75.96%.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #11525   +/-   ##
=======================================
  Coverage   75.96%   75.96%           
=======================================
  Files         207      207           
  Lines        5942     5942           
  Branches     2302     2302           
=======================================
  Hits         4514     4514           
  Misses       1378     1378           
  Partials       50       50           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@netlify
Copy link

netlify bot commented Aug 5, 2023

Deploy Preview for react-navigation-example ready!

Name Link
🔨 Latest commit 9b53010
🔍 Latest deploy log https://app.netlify.com/sites/react-navigation-example/deploys/663eae9820fd390008ce281a
😎 Deploy Preview https://deploy-preview-11525--react-navigation-example.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

: Route<Extract<RouteName, string>, ParamList[RouteName]>;
}[keyof ParamList];

type MaybeParamListRoute<ParamList extends {}> = ParamList extends ParamListBase
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less than sure on the naming for this one. Need a second type to manage the case were ParamList does not extend ParamListBase, as the restriction on the ref is extends {}.

Would need the name to signify that it might be a "well typed" Route or it could be Route<string> depending on what you pass.

Copy link
Member

@osdnk osdnk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please check in tests if works for the nested routes?

@lucasloisp
Copy link
Author

lucasloisp commented Nov 23, 2023

So, I've been playing with that. It seems like StaticParamList adds an | undefined to nested navigators, which does not happen on the "common" navigator definitions.

const DemoStackToNest = createStackNavigator({
  screens: {
    Groups: () => null,
    Chat: (_: StaticScreenProps<{id: number}>) => null,
  }
})
const DemoStackWithNested = createStackNavigator({
  screens: {
    Home: DemoStackToNest
  }
})
type DemoParamListOfNest = StaticParamList<typeof DemoStackToNest>
type DemoParamList = StaticParamList<typeof DemoStackWithNested>
declare const pl: DemoParamList
expectTypeOf(pl['Home']).toEqualTypeOf<NavigatorScreenParams<DemoParamListOfNest> | undefined>()
expectTypeOf(pl['Home']).toEqualTypeOf<NavigatorScreenParams<DemoParamListOfNest>>() // Fails TS, missing undefined

type CommonDemoParamListOfNest = {
  Groups: undefined
  Chat: { id: number }
}
type CommonDemoParamList = {
  Home: NavigatorScreenParams<CommonDemoParamListOfNest>
}
declare const plc: CommonDemoParamList
expectTypeOf(plc['Home']).toEqualTypeOf<NavigatorScreenParams<DemoParamListOfNest> | undefined>() // Fails TS, extra undefined
expectTypeOf(plc['Home']).toEqualTypeOf<NavigatorScreenParams<DemoParamListOfNest>>()

Working on that one and will then add tests for the new Route type

@lucasloisp
Copy link
Author

Seems to stem from ParamsForScreen having an explicit | undefined on it:

? NavigatorScreenParams<StaticParamList<T>> | undefined

This type, ultimately, seems to only be used for defining StaticParamList.
Any reason not to make that no longer be optional? codebase itself passes typechecks after the change.

@satya164
Copy link
Member

satya164 commented Mar 9, 2024

Any reason not to make that no longer be optional?

Because you can navigate to a screen without needing to specify any child screens - which renders the initial route defined in the navigator. The types need to handle | undefined. It's not specific to static config, the same can be done in dynamic config.

@@ -46,7 +46,7 @@ type ParamsForScreenComponent<T> = T extends {
type ParamsForScreen<T> = T extends { screen: StaticNavigation<any, any, any> }
? NavigatorScreenParams<StaticParamList<T['screen']>> | undefined
: T extends StaticNavigation<any, any, any>
? NavigatorScreenParams<StaticParamList<T>> | undefined
? NavigatorScreenParams<StaticParamList<T>>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
? NavigatorScreenParams<StaticParamList<T>>
? NavigatorScreenParams<StaticParamList<T>> | undefined

@lucasloisp lucasloisp force-pushed the feat/type-safe-ref-get-current-route branch from 72eea65 to 9b53010 Compare May 10, 2024 23:32
@lucasloisp
Copy link
Author

Because you can navigate to a screen without needing to specify any child screens - which renders the initial route defined in the navigator. The types need to handle | undefined. It's not specific to static config, the same can be done in dynamic config.

Got it! Missed that on common as those types are defined as non-undefined on the test. Having a hard time figuring in out ParamListRoute with this though - kinda need to check ParamList[RouteName] extends (NavigatorScreenParams<infer T extends ParamListBase> OR undefined) in a sense, but then recursively put in those "undefined" values in the right place.

I'll post updates if I can make progress!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants