usePreventRemove
The usePreventRemove
hook allows you to prevent the user from leaving a screen. For example, if there are unsaved changes, you might want to show a confirmation dialog before the user can navigate away.
The hook takes 2 parameters:
preventRemove
: A boolean value indicating whether to prevent the screen from being removed.callback
: A function that will be called when the removal is prevented. This can be used to show a confirmation dialog.
The callback receives a data
object with the action
that triggered the removal of the screen. You can dispatch this action again after confirmation, or check the action object to determine what to do.
Example:
- Static
- Dynamic
const EditTextScreen = () => {
const [text, setText] = React.useState('');
const navigation = useNavigation();
const hasUnsavedChanges = Boolean(text);
usePreventRemove(hasUnsavedChanges, ({ data }) => {
if (Platform.OS === 'web') {
const discard = confirm(
'You have unsaved changes. Discard them and leave the screen?'
);
if (discard) {
navigation.dispatch(data.action);
}
} else {
Alert.alert(
'Discard changes?',
'You have unsaved changes. Discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
onPress: () => navigation.dispatch(data.action),
},
]
);
}
});
return (
<View style={styles.content}>
<TextInput
autoFocus
style={styles.input}
value={text}
placeholder="Type something…"
onChangeText={setText}
/>
</View>
);
};
const EditTextScreen = () => {
const navigation = useNavigation();
const [text, setText] = React.useState('');
const hasUnsavedChanges = Boolean(text);
usePreventRemove(hasUnsavedChanges, ({ data }) => {
if (Platform.OS === 'web') {
// Alert is not supported on web, so we can use confirm
const discard = confirm(
'You have unsaved changes. Discard them and leave the screen?'
);
if (discard) {
navigation.dispatch(data.action);
}
} else {
// Prompt the user before leaving the screen
Alert.alert(
'Discard changes?',
'You have unsaved changes. Discard them and leave the screen?',
[
{
text: "Don't leave",
style: 'cancel',
onPress: () => {
// Do nothingP
},
},
{
text: 'Discard',
style: 'destructive',
onPress: () => navigation.dispatch(data.action),
},
]
);
}
});
return (
<View style={styles.content}>
<TextInput
autoFocus
style={styles.input}
value={text}
placeholder="Type something…"
onChangeText={setText}
/>
</View>
);
};
Internally, the hook uses the beforeRemove
event to prevent the screen from being removed. This event is triggered whenever a screen is being removed due to a navigation action.
Limitations
There are a couple of limitations to be aware of when using the usePreventRemove
hook. It is only triggered whenever a screen is being removed due to a navigation state change. For example:
- The user pressed the back button on a screen in a stack.
- The user performed a swipe-back gesture.
- Some action such as
pop
orreset
was dispatched which removes the screen from the state.
It does not prevent a screen from being unfocused if it's not being removed. For example:
- The user pushed a new screen on top of the screen with the listener in a stack.
- The user navigated from one tab/drawer screen to another tab/drawer screen.
It also does not prevent a screen from being removed when the user is exiting the screen due to actions not controlled by the navigation state:
- The user closes the app (e.g. by pressing the back button on the home screen, closing the tab in the browser, closing it from the app switcher etc.). You can additionally use
hardwareBackPress
event on Android,beforeunload
event on the Web etc. to handle some of these cases. See Prevent the user from leaving the app for more details. - A screen gets unmounted due to conditional rendering, or due to a parent component being unmounted.
- A screen gets unmounted due to the usage of
unmountOnBlur
options with@react-navigation/bottom-tabs
,@react-navigation/drawer
etc.
UX considerations
Generally, we recommend using this hook sparingly. A better approach is to persist the unsaved data into AsyncStorage
or similar persistent storage and prompt to restore it when the user returns to the screen.
Doing so has several benefits:
- This approach still works if the app is closed or crashes unexpectedly.
- It's less intrusive to the user as they can still navigate away from the screen to check something and return without losing the data.