/** | |
* These hooks re-implement the now removed useBlocker and usePrompt hooks in 'react-router-dom'. | |
* Thanks for the idea @piecyk https://github.com/remix-run/react-router/issues/8139#issuecomment-953816315 | |
* Source: https://github.com/remix-run/react-router/commit/256cad70d3fd4500b1abcfea66f3ee622fb90874#diff-b60f1a2d4276b2a605c05e19816634111de2e8a4186fe9dd7de8e344b65ed4d3L344-L381 | |
*/ | |
import { useContext, useEffect, useCallback } from 'react'; | |
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom'; | |
/** | |
* Blocks all navigation attempts. This is useful for preventing the page from | |
* changing until some condition is met, like saving form data. | |
* | |
* @param blocker | |
* @param when | |
* @see https://reactrouter.com/api/useBlocker | |
*/ | |
export function useBlocker( blocker, when = true ) { | |
const { navigator } = useContext( NavigationContext ); | |
useEffect( () => { | |
if ( ! when ) return; | |
const unblock = navigator.block( ( tx ) => { | |
const autoUnblockingTx = { | |
...tx, | |
retry() { | |
// Automatically unblock the transition so it can play all the way | |
// through before retrying it. TODO: Figure out how to re-enable | |
// this block if the transition is cancelled for some reason. | |
unblock(); | |
tx.retry(); | |
}, | |
}; | |
blocker( autoUnblockingTx ); | |
} ); | |
return unblock; | |
}, [ navigator, blocker, when ] ); | |
} | |
/** | |
* Prompts the user with an Alert before they leave the current screen. | |
* | |
* @param message | |
* @param when | |
*/ | |
export function usePrompt( message, when = true ) { | |
const blocker = useCallback( | |
( tx ) => { | |
// eslint-disable-next-line no-alert | |
if ( window.confirm( message ) ) tx.retry(); | |
}, | |
[ message ] | |
); | |
useBlocker( blocker, when ); | |
} |
const MyComponent = () => { | |
const formIsDirty = true; // Condition to trigger the prompt. | |
usePrompt( 'Leave screen?', formIsDirty ); | |
return ( | |
<div>Hello world</div> | |
); | |
}; |
if i use this workaround: https://stackoverflow.com/a/71587163/8046487
i receive an exception: navigator.block is not a function. Anyone else having this issue?
i am using the newest react router version 6.4
You can check the original github issue that led to the creation of this gist: remix-run/react-router#8139 (comment)
Just fyi, it's still possible to use navigator.block via history package with v6.4.0, by replacing BrowserRouter with unstable_HistoryRouter
Resetting any of the dependencies at line 38 when canceling the navigation solved the TODO for me.
Thanks for sharing!
if i use this workaround: https://stackoverflow.com/a/71587163/8046487 i receive an exception: navigator.block is not a function. Anyone else having this issue?
i am using the newest react router version 6.4
@borstelmannl I am also having this issue on 6.4
hey @alexrabin,
just replace your Browserrouter or any other router you have with the unstable_HistoryRouter. Then the example should work perfectly fine.
Is there a way to use unstable_HistoryRouter with createBrowserRouter? The current react router tutorial uses createBrowserRouter.
@bufke did you find a way to solve this with createBrowserRouter ?
I did not, I ended up using <BrowserRouter>
.
This approach may not be needed soon as we now have (as of v6.7.0) unstable_useBlocker
and unstable_usePrompt
:
remix-run/react-router#8139 (comment)
Waiting for the docs to be written before I update this gist.
Not woking with some gaming mouse have back button, any ideals?
Here's a useBeforeUnload hook for v6.4.3 that catches both react router navigation and browser navigation:
import { useCallback } from 'react';
import { useBeforeUnload as _useBeforeUnload, unstable_usePrompt as usePrompt } from 'react-router-dom';
export default function useBeforeUnload(doBlock?: boolean) {
_useBeforeUnload(
useCallback(e => {
if (doBlock) {
e.preventDefault();
return e.returnValue = '';
}
}, [doBlock])
);
usePrompt({
when: doBlock,
message: 'Discard unsaved changes?'
});
}
thx!
Here's a useBeforeUnload hook for v6.4.3 that catches both react router navigation and browser navigation:
import { useCallback } from 'react'; import { useBeforeUnload as _useBeforeUnload, unstable_usePrompt as usePrompt } from 'react-router-dom'; export default function useBeforeUnload(doBlock?: boolean) { _useBeforeUnload( useCallback(e => { if (doBlock) { e.preventDefault(); return e.returnValue = ''; } }, [doBlock]) ); usePrompt({ when: doBlock, message: 'Discard unsaved changes?' }); }
This is awesome, thanks for sharing! 💯
you can simply add these:
useEffect(() => {
window.addEventListener("beforeunload", onBeforeUnload);
return () => {
window.removeEventListener("beforeunload", onBeforeUnload);
};
});
const onBeforeUnload = (e) => {
if (hasDirty) {
e.preventDefault();
e.returnValue = "";
}
};
Reference : https://claritydev.net/blog/display-warning-for-unsaved-form-data-on-page-exit
you can simply add these:
useEffect(() => { window.addEventListener("beforeunload", onBeforeUnload); return () => { window.removeEventListener("beforeunload", onBeforeUnload); }; }); const onBeforeUnload = (e) => { if (hasDirty) { e.preventDefault(); e.returnValue = ""; } };
Reference : https://claritydev.net/blog/display-warning-for-unsaved-form-data-on-page-exit
This doesn't fire during route transitions...
Works like a charm. Thanks for sharing! 💯