Listen for dismiss event react navigation

Viewed 2018

I have a modal in react navigation, written as a functional component. On IOS it can be dismissed just by swiping down. So i don't have a back button or anything.

I would like to be able to listen for dismiss event on the modal, so i can call a function that clears (redux) state and fetch new data when the modal closes.

The navigation.addListener('willBlur') event doesn't work, because that is also triggered when I open another modal from that modal.

useEffect seems promising, but I can't make it work. Every time the dependency changes, the effect cleanup is run.

    const filters = useSelector(taskFilters);
    const dispatch = useDispatch();
    const isFirstRun = useRef(true);
    useEffect(() => {
        // Skip first render
        if (isFirstRun.current) {
            isFirstRun.current = false;
            return;
        }
        // effect cleanup
        return () => {
            dispatch(cleanUpAndFetch(filters));
        };
    }, [filters]);

If I do like this, it only gets called when the component dismounts. But now I have an old version of filters, because they are not provided as dependencies. And I only want to call cleanUpAndFetch if the data has been changed, so i still need to take care of that also

  useEffect(() => {
        return () => {
            dispatch(cleanUpAndFetch(filters));
        };
    }, []);

To summarize. Is there a way to listen for dismiss event react navigation?

I use the newest version of Expo and React Navigation v.4

Edit:

The modal is created by navigation configuration, something like this:

export const RootStack = createStackNavigator(
    {
        Main: {
            screen: RootSwitchStack,
            navigationOptions: {
                headerShown: false,
                headerBackTitleVisible: false
            }
        },
        MyModal,
        ChildModal // Gets opened from MyModal
    },
    {
        initialRouteName: 'Main',
        mode: 'modal',
        defaultNavigationOptions: {
            ...headerSetup,
            ...modalTransitions
        }
    }
);

Where MyModal is the one where I need something like onRequestClose

2 Answers

I am still looking for a solution but I have implemented following work around:

The IOS modal will use default transition, and can only be dismissed using header back button.

import {
    createStackNavigator,
    TransitionPresets
} from 'react-navigation-stack';
...
export const RootStack = createStackNavigator(
    {
        Main: {
            screen: RootSwitchStack,
            navigationOptions: {
                headerShown: false,
                headerBackTitleVisible: false
            }
        },
        MyModal: { 
            screen: MyModal,
            navigationOptions: ({navigation}) => ({
                // Our previous were ModalPresentationIOS, but we cannot know
                // when the component has been dismissed, so we use a modal with 
                // back button
                ...TransitionPresets.DefaultTransition,
                headerLeft: () => <HeaderBack navigation={navigation} />
            })
        },
        ChildModal // Gets opened from MyModal
    },
    {
        initialRouteName: 'Main',
        mode: 'modal',
        defaultNavigationOptions: {
            ...headerSetup,
            ...modalTransitions
        }
    }
);

import { HeaderBackButton } from 'react-navigation-stack';

export const HeaderBack = ({
    navigation
}: {
    navigation: NavigationStackProp;
}) => {
    const filters = useSelector(taskFilters);
    const [initialFilters] = useState(filters);

    const dispatch = useDispatch();

    // We use a bit more complex comparison, but geist of it is the same
    const isFiltersTheSameAsInitial = initialFilters === filters;

    return (
        <HeaderBackButton
            labelVisible={false}
            onPress={() => {
                if (!isFiltersTheSameAsInitial) {
                    dispatch(cleanUpAndFetch(filters));
                }
                navigation.pop();
            }}
            tintColor={colors.black}
        />
    );
};

The 'Dismiss' event in iOS can be handled by the beforeRemove navigation event / listener in version 5.x (not sure if it's available in 4.x).

Check out these docs: https://reactnavigation.org/docs/navigation-events/

You can listen to this event in multiple ways, but here is one example:

screenListeners = {
    focus: () => {
        // do something when screen opens & is focused
    },
    beforeRemove: () => {
        // do something when scree is closed OR dismissed (in iOS)
    }
};

and when rendering later...

<RootStack.Navigator mode="modal">
    <RootStack.Screen
        name="MyScreen"
        component={MyScreenStackNavigator}
        listeners={screenListeners}
    />
</RootStack.Navigator>

Other example from docs: https://reactnavigation.org/docs/navigation-events/#listeners-prop-on-screen

Related