There's a certain page in my React app that I would like to prevent the user from leaving if the form is dirty.
In my react-routes, I am using the onLeave prop like this:
<Route path="dependent" component={DependentDetails} onLeave={checkForm}/>
And my onLeave
is:
const checkForm = (nextState, replace, cb) => {
if (form.IsDirty) {
console.log('Leaving so soon?');
// I would like to stay on the same page somehow...
}
};
Is there a way to prevent the new route from firing and keep the user on the same page?
React router has a hook called useBlocker You can use it to prevent navigating away from a page.
You can use the following hook to show a confirm dialog with cancel and confirm callbacks:
import { useCallback, useEffect, useState } from 'react';
import { useBlocker, useNavigate } from 'react-router-dom';
export const useUnsavedChangesGuard = (hasUnsavedChanges) => {
const navigate = useNavigate();
const [nextLocation, setNextLocation] = useState(null);
const [blockerEnabled, setBlockerEnabled] = useState(hasUnsavedChanges);
useEffect(() => {
setBlockerEnabled(hasUnsavedChanges); // Sync blocker state with hasUnsavedChanges
}, [hasUnsavedChanges]);
const handleNavigationConfirm = useCallback(() => {
if (nextLocation) {
setBlockerEnabled(false); // Disable the blocker
setTimeout(() => {
navigate(nextLocation.pathname); // Navigate after ensuring blocker is unset
}, 0);
setNextLocation(null); // Clear the stored location
}
}, [nextLocation, navigate]);
const handleNavigationCancel = useCallback(() => {
setNextLocation(null); // Reset the stored location
}, []);
const blocker = useCallback(
({ nextLocation }) => {
if (blockerEnabled && hasUnsavedChanges) {
setNextLocation(nextLocation); // Store the location to navigate later
return true; // Block navigation
}
return false; // Allow navigation
},
[blockerEnabled, hasUnsavedChanges, nextLocation]
);
useBlocker(blocker);
return {
handleNavigationConfirm,
handleNavigationCancel,
isBlocked: Boolean(nextLocation),
};
};
To show or hide the popup you can subscribe to the isBlocked
property
const { handleNavigationConfirm, handleNavigationCancel, isBlocked } = useUnsavedChangesGuard(isFormDirty);
useEffect(() => {
if (isBlocked) {
confirmRef.current?.showConfirm();
} else {
confirmRef.current?.hideConfirm();
}
}, [isBlocked]);
// or if you have something like this
<Confirm isOpen={isBlocked} onConfirm={handleNavigationConfirm} onCancel={handleNavigationCancel}>