My app has two pages: Step1
and Step2
. Step1
has a checkbox that blocks navigation if it is checked and a Next button that navigates to Step2
when clicked. Step2
has a Previous button that navigates back to Step1
when clicked.
As per this tutorial , I'm using the block
method of the createBrowserHistory
object to block route changes if the checkbox in Step1
is checked:
const unblock = useRef();
useEffect(() => {
unblock.current = history.block((tx) => {
if (block.current.checked) {
const promptMessage = "Are you sure you want to leave?";
if (window.confirm(promptMessage)) {
unblock.current();
tx.retry();
}
} else {
console.log("tfgx");
unblock.current();
tx.retry();
}
});
}, []);
I also had to set the history prop in the low-level <Router>
(not <BrowserRouter>
) to the createBrowserHistory
object, like so:
<Router history={createBrowserHistory()}>
...
</Router>
But this prevents the routes from being rendered properly . I think this may have something to do with <Switch>
not being able to read the location object properly. If I use <BrowserRouter>
, the location object looks like this: {pathname: "/step1", ... key: "7sd45"}
. But when I use <Router={createBrowserHistory()}>
, the location object looks like this {action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}}
. (I'm also getting the warning You cannot change <Router history>
.)
My desired result is to block navigation if the 'Block navigation' checkbox is checked and unblock it otherwise. If the location changes when navigation is unblocked, I would like the corresponding route to be rendered correctly.
The section on createBrowserHisory in the React Router v5 docs is sparse and there aren't many examples that make use of it, so I'd be grateful if someone could shed some light on this.
EDIT: Passing location.location
to <Switch>
seems to fix it ( Updated demo ). But if I call useLocation
inside Step1
and print the result (line 17-18), I get {pathname: "/step1", ... key: "7sd45"}
and not {action: "PUSH", location: {pathname: "/step1", ... key: "7sd45"}}
. Why is this?
Also, if the user attempts to go to another location when navigation is blocked, my custom prompt appears as expected ("Are you sure you want to leave" with "OK" and "Cancel" buttons). However, if they dismiss this by clicking Cancel, then the browser's own dialog box appears -
In Chrome:
In Firefox:
Is it possible to suppress the browser prompt after my prompt has been dismissed?
The router context's history
object also has a block function but it works a little differently. It takes a callback that consumes location
and action
arguments.
history.block((location, action) => {...});
Returning false
from the callback blocks the navigation transition, returning true
allows the transition to go through.
React.useEffect(() => {
const unblock = history.block((location, action) => {
if (checkBlockingCondition) {
return window.confirm("Navigate Back?");
}
return true;
});
return () => {
unblock();
};
}, []);
Alternatively, react-router-dom suggests using the Prompt
component to conditionally block route transitions. Your code is very close to their preventing transitions example.
Updates to your last codesandbox:
Prompt
component.code
import {
BrowserRouter as Router,
Prompt, // <-- import Prompt
Redirect,
Switch,
Route,
useLocation
} from "react-router-dom";
const Step1 = ({ id, history }) => {
const [isBlocking, setIsBlocking] = useState(false);
return (
<form
id={id}
onSubmit={(e) => {
e.preventDefault(); // <-- prevent default form action, i.e. page reload
history.push("/step2");
}}
>
<label>
Block navigation
<input
type="checkbox"
onChange={(e) => setIsBlocking(e.target.checked)}
/>
</label>
<br />
<br />
<button type="submit">Next</button>
<Prompt
when={isBlocking} // <-- blocking condition
message="Are you sure you want to leave?"
/>
</form>
);
};
To do this in typescript, you could use a ternary based on your "dirty" condition to show the prompt before navigating to your next route.
As long as the component this goes in is inside of aa routing context, it will work.
isChecked
is a psuedo-code variable to be replaced by whatever condition you want to evaluate on leaving the page. I recommend having it in the dependencies because when the state variable updates, so will the block condition.
const history = useHistory();
useEffect(() => {
const unblock = history.block(
!isChecked
? "You’ve got unsaved changes. Are you sure you want to navigate away from this page?"
: true
);
return function cleanup() {
unblock();
};
}, [isChecked]);
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.