I have an issue with Typescript and React Navigation for my React Native app.
I've got the following setup in my project, which gives me awesome help and autocompletion when using e.g. navigation.push().
Types
import { StackNavigationProp } from '@react-navigation/stack';
export type RootStackParamList = {
Home: undefined;
Content: undefined;
List: undefined;
};
export type StackNavigationProps = StackNavigationProp<RootStackParamList>;
Usage
const navigation = useNavigation<StackNavigationProps>();
However, I've created a navigation handler function that takes a parameter that gets fed to navigation.push. It works fine as long as the type of the parameter is of type any.
const navigationHandler = useCallback((targetScreen) => {
navigation.push(targetScreen);
}, [navigation]);
But I am not satisfied with the any type and would prefer that targetScreen could in fact just be one of the screens that were declared in my RootStackParamList type. I have tried in countless ways to satisfy the push method of the navigation object but I'm stuck.
The following attempt has been the most successful but is yet not working exactly as it should. It does give me autocompletion on available screens, but the push is still unhappy.
Type:
export type NavigationScreen<T extends RootStackParamList> = {
[K in keyof T]: K;
}[keyof T];
Usage:
const navigationHandler = useCallback(
<T extends RootStackParamList>(targetScreen: NavigationScreen<T>) => {
navigation.push(targetScreen);
},[navigation]);
Hovering the error I get prompted with the following:
(parameter) targetScreen: { [K in keyof T]: K; }[keyof T] Argument of type '[{ [K in keyof T]: K; }[keyof T]]' is not assignable to parameter of type '["Home" | "Content" | "List"] | ["Home" | "Content" | "List", undefined]'. Type '[{ [K in keyof T]: K; }[keyof T]]' is not assignable to type '["Home" | "Content" | "List"]'.
Type 'keyof T' is not assignable to type '"Home" | "Content" | "List"'.
Type 'string | number | symbol' is not assignable to type '"Home" | "Content" | "List"'.
Type 'string' is not assignable to type '"Home" | "Content" | "List"'.
Type 'keyof T' is not assignable to type '"List"'.
Type 'string | number | symbol' is not assignable to type '"List"'.
Type 'string' is not assignable to type '"List"'.
How to get this working?
If you're just interested in using the name of the screen for your navigationHandler function but not the params, you can do as @artcorpse suggested in his comment to your question and use keyof RootStackParamList:
const navigationHandler = useCallback(
(targetScreen: keyof RootStackParamList) => {
navigation.push(targetScreen);
},
[navigation]
);
If, on the other hand, you want to allow for params, you've got two options:
const navigationHandler = useCallback(
<T extends keyof RootStackParamList>(
targetScreen: T,
targetScreenParams?: RootStackParamList[T]
) => {
navigation.push(
targetScreen as keyof RootStackParamList,
targetScreenParams
);
},
[navigation]
);
The upside of this option is that route parameter object shapes are checked against the corresponding routes as defined in the RootStackParamList type. For example,
type RootStackParamList = {
Default: undefined,
Home: {a: 'a'};
Profile: { userId: string };
};
navigationHandler('Home', {a: 'a'}); //OK
navigationHandler('Home', {userId: 'a'}); //ERROR
const navigationHandler2 = useCallback(
(
targetScreen: Parameters<typeof navigation.push>[0],
targetScreenParams?: Parameters<typeof navigation.push>[1]
) => {
navigation.push(targetScreen, targetScreenParams);
},
[navigation]
);
This option takes the existing types you've defined and attempts to extract them from the navigation.push method. The downside here is that you won't get the above mentioned check regarding routes and their corresponding parameter object shapes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With