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.

https://media.giphy.com/media/FcMdPWSY547cAPhStD/giphy.gif

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;

https://media.giphy.com/media/S4A8Vj7sjIvazucZ0r/giphy.gif

If you want every stack to reset when changing tabs you can remove the tabName === TAB_TO_RESET check.