I have a triple nested stack navigator in my bare bone React Native application with @react-navigation/native v6.
Despite many attempts, I fail to properly type the useNavigation hook. It is functional, but I cannot get rid of typescript complaints.
RootNavigator with the global namespace definition:
export type RootStackParamList = {
ModalNavigator: NavigatorScreenParams<ModalStackParamList>;
};
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
const Stack = createStackNavigator<RootStackParamList>();
export const RootNavigator = () => {
return (
<Stack.Navigator initialRouteName="ModalNavigator">
<Stack.Screen name="ModalNavigator" component={ModalNavigator} />
</Stack.Navigator>
);
};
ModalNavigator:
export type ModalStackParamList = {
ChildStackA: NavigatorScreenParams<ChildStackAParamList>;
ChildStackB: NavigatorScreenParams<ChildStackBParamList>;
};
const Stack = createStackNavigator<ModalStackParamList>();
export const ModalNavigator = () => {
return (
<Stack.Navigator initialRouteName="ChildStackA">
<Stack.Screen name="ChildStackA" component={ChildNavigatorA} />
<Stack.Screen name="ChildStackB" component={ChildNavigatorB} />
</Stack.Navigator>
);
};
ChildNavigatorA:
export type ChildStackAParamList = {
ScreenA: undefined;
ScreenB: undefined;
};
const StackA = createStackNavigator<ChildStackAParamList>();
export const ChildNavigatorA = () => {
return (
<StackA.Navigator initialRouteName="ScreenA">
<StackA.Screen name="ScreenA" component={ScreenA} />
<StackA.Screen name="ScreenB" component={ScreenB} />
</StackA.Navigator>
);
};
In my ScreenA, I want to have a component that utilizes useNavigation() to navigate to ScreenB like this:
type ScreenAProps = CompositeScreenProps<
StackScreenProps<ChildStackAParamList, 'ScreenA'>,
CompositeScreenProps<
StackScreenProps<ModalStackParamList>,
StackScreenProps<RootStackParamList>
>
>;
export const ScreenA = ({navigation}: ScreenAProps) => {
return (
<View style={styles.view}>
<Text style={styles.text}>Screen A</Text>
<NavigateButton />
</View>
);
};
const NavigateButton = () => {
const {navigate} = useNavigation();
return <Button title="To ScreenB" onPress={() => navigate('ScreenB')} />;
};
While the code itself is functional, I get this typescript complaint:
No overload matches this call. Argument of type '[string]' is not assignable to parameter of type 'never'. Overload 2 of 2, '(options: never): void', gave the following error. Argument of type 'string' is not assignable to parameter of type 'never'.
How can I properly solve this problem?
Important notes:
- As recommended by the documentation, I do NOT want to directly type
useNavigation
like thisuseNavigation<SomeNavigationProp>()
- I specifically want to use useNavigation, not the navigation screen props in this case
- I don’t want to do something like
as any
- If possible, I could directly call
navigate('ScreenB')
, notnavigate('ChildStackA', {screen: 'ScreenB'})
. This worked in v5, no complaints.
2
Answers
n React Navigation v6, the navigate function expects a screen name as the first argument, not just any string. Since TypeScript can’t infer the possible screen names dynamically, you need to provide a type hint to let TypeScript know what are the possible screen names.
You can do this by defining a type that represents the possible screen names of your navigation stack and then use this type to annotate the navigate function.
`
`
It’s impossible to use it like that in a typesafe way as typescript doesn’t know your component structure.
This is the appropriate way to do it if you want type safety, even though it is verbose:
Also, the reason it is so verbose is because of multiple nested navigators. There’s no reason to nest multiple stack navigators. It is recommended to keep your navigation tree as flat as possible https://reactnavigation.org/docs/nesting-navigators/#best-practices-when-nesting
If you flatten your navigation tree to a single stack, then this will be like you want.