React Navigation v5: Reset Stack Inside Tab After Leaving Tab
Originally published at reactnativeschool.com
Problem: You have a stack navigator inside a tab and, when going to a different tab, you want to reset the stack navigator inside the tab you just left.
Example
import * as React from 'react';
import { View, Text, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { CommonActions, StackActions } from '@react-navigation/native';
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button title="Details" onPress={() => navigation.push('Details')} />
</View>
);
}
function DetailsScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Details Screen</Text>
</View>
);
}
function SettingsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Settings Screen</Text>
<Button
title="Settings Details"
onPress={() => navigation.push('SettingsDetail')}
/>
</View>
);
}
function SettingsDetailScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Settings Details Screen</Text>
</View>
);
}
const Stack = createStackNavigator();
const Stack2 = createStackNavigator();
const Tab = createBottomTabNavigator();
const HomeStack = () => (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
const SettingsStack = () => (
<Stack2.Navigator>
<Stack2.Screen name="Settings" component={SettingsScreen} />
<Stack2.Screen name="SettingsDetail" component={SettingsDetailScreen} />
</Stack2.Navigator>
);
function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="HomeTab" component={HomeStack} />
<Tab.Screen name="SettingsTab" component={SettingsStack} />
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
For this example app I only want to reset the home stack if I leave that tab. If I'm in a nested screen on settings I want to leave it as is.
Solution
This isn't a great solution (notice the "dangerouslyGetState") but it seems to work well enough. This solution is built on top of the solution provided in this Github issue.
// ...
// https://github.com/react-navigation/react-navigation/issues/8583
const TAB_TO_RESET = 'HomeTab';
const resetHomeStackOnTabPress = ({ navigation, route }) => ({
tabPress: (e) => {
const state = navigation.dangerouslyGetState();
if (state) {
// Grab all the tabs that are NOT the one we just pressed
const nonTargetTabs = state.routes.filter((r) => r.key !== e.target);
nonTargetTabs.forEach((tab) => {
// Find the tab we want to reset and grab the key of the nested stack
const tabName = tab?.name;
const stackKey = tab?.state?.key;
if (stackKey && tabName === TAB_TO_RESET) {
// Pass the stack key that we want to reset and use popToTop to reset it
navigation.dispatch({
...StackActions.popToTop(),
target: stackKey,
});
}
});
}
},
});
function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen
name="HomeTab"
component={HomeStack}
// Note that resetHomeStackOnTabPress should be added to each tab
listeners={resetHomeStackOnTabPress}
/>
<Tab.Screen
name="SettingsTab"
component={SettingsStack}
listeners={resetHomeStackOnTabPress}
/>
</Tab.Navigator>
</NavigationContainer>
);
}
export default App;
If you want every stack to reset when changing tabs you can remove the tabName === TAB_TO_RESET
check.