Reputation: 95
What I want to achieve is straightforward. I want the user to be forced to confirm exiting a tab navigator called 'checkout'.
I read on React Navigation docs about preventing going back about the 'beforeRemove' event which seems neat and the right thing to use.
The problem is that in their example they call Alert in the eventlistener whereas I want to show a custom modal with a yes and no button.
This is React Navigations example code:
function EditText({ navigation }) {
const [text, setText] = React.useState('');
const hasUnsavedChanges = Boolean(text);
React.useEffect(
() =>
navigation.addListener('beforeRemove', (e) => {
if (!hasUnsavedChanges) {
// If we don't have unsaved changes, then we don't need to do anything
return;
}
// Prevent default behavior of leaving the screen
e.preventDefault();
// Prompt the user before leaving the screen
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
// If the user confirmed, then we dispatch the action we blocked earlier
// This will continue the action that had triggered the removal of the screen
onPress: () => navigation.dispatch(e.data.action),
},
]
);
}),
[navigation, hasUnsavedChanges]
);
return (
<TextInput
value={text}
placeholder="Type something…"
onChangeText={setText}
/>
);
}
This is the code I have tried:
useEffect(() => {
navigation.addListener('beforeRemove', e => {
if (userConfirmedExit) {
navigation.dispatch(e.data.action);
} else {
e.preventDefault();
setShowExitModal(true);
}
});
}, [navigation, userConfirmedExit]);
const handleConfirmExit = () => {
setUserConfirmedExit(true);
navigation.replace('ProfileTab');
};
const handleDeclineExit = () => setShowExitModal(false);
I am bound to use the navigation.dispatch(e.data.action) inside the eventListener but the handleConfirmExit function must live outside of it and I just can't figure out how to use the beforeRemove listener AND showing a custom modal from where I can exit the tab.
The listener is firing when pressing the back button and the modal shows but nothing happens when pressing yes (i.e running the handleConfirmExit function).
I have tried removing dependencies from the useEffect. The one thing that did work, but only on Android was this:
useEffect(() => {
navigation.addListener('beforeRemove', e => {
e.preventDefault();
setShowExitModal(true);
});
}, [navigation]);
const handleConfirmExit = () => {
navigation.removeListener('beforeRemove', () => {});
navigation.replace('ProfileTab');
};
const handleDeclineExit = () => setShowExitModal(false);
On iOS the modal stays onto the next screen for some reason and the culprit I think is the bad implementation of 'beforeRemove' listener in the last example.
Thank you!
Upvotes: 3
Views: 7480
Reputation: 1
You can store the e.data.action in a state and then use this action by calling navigation.dispath(navigationAction) like below;
const [navigationState, setNavigationState] = useState(null);
useEffect(() => {
const subscribe = navigation.addListener('beforeRemove', (e) => {
e.preventDefault();
setExitModal(true)
setNavigationAction(e.data.action)
}
},[])
and then call the navigation dispatch action
Upvotes: 0
Reputation: 41
I have a simple solution
navigation.addListener('beforeRemove', (e) => {
if (e.data.action.type !="GO_BACK") {
//"GO_BACK" is emitted by hardware button
navigation.dispatch(e.data.action);
} else {
//your code to prevent hardware back button goes here } //
} )
Upvotes: 4
Reputation: 95
This is what I did and it works fine, but I am sure there is a better solution out there.
const [showExitModal, setShowExitModal] = useState(false);
let exitEvent = useRef<
EventArg<
'beforeRemove',
true,
{
action: Readonly<{
type: string;
payload?: object | undefined;
source?: string | undefined;
target?: string | undefined;
}>;
}
>
>();
useEffect(() => {
const unsubscribe = navigation.addListener('beforeRemove', e => {
e.preventDefault();
exitEvent.current = e;
setShowExitModal(true);
});
return unsubscribe;
}, [navigation]);
const handleConfirmExit = () => {
if (exitEvent.current) {
navigation.dispatch(exitEvent.current.data.action);
}
};
In the markup:
{showExitModal && (
<CheckOutExitModal
onYesPress={handleConfirmExit}
/>
)}
Upvotes: 1
Reputation: 428
use BackHandler , you can use navigation.goBack() instead of BackHandler.exitApp()
import { BackHandler} from "react-native";
const backAction = () => {
Alert.alert("Discard changes?", "Are you sure you want to exit?", [
{
text: "NO",
onPress: () => null,
style: "cancel"
},
{ text: "YES", onPress: () => BackHandler.exitApp() }
]);
return true;
};
useEffect(() => {
BackHandler.addEventListener("hardwareBackPress", backAction);
return () => {
BackHandler.removeEventListener("hardwareBackPress", backAction);
}
}, []);
Upvotes: 0