Skip to content

Instantly share code, notes, and snippets.

@rmorse
Last active December 21, 2023 04:22
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save rmorse/426ffcc579922a82749934826fa9f743 to your computer and use it in GitHub Desktop.
Save rmorse/426ffcc579922a82749934826fa9f743 to your computer and use it in GitHub Desktop.
Adds back in `useBlocker` and `usePrompt` to `react-router-dom` version 6.0.2 (they removed after the 6.0.0 beta, temporarily)
/**
* 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>
);
};
@borstelmannl
Copy link

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

@matrey
Copy link

matrey commented Sep 19, 2022

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

@fcmdvtk4r
Copy link

Resetting any of the dependencies at line 38 when canceling the navigation solved the TODO for me.

Thanks for sharing!

@alexrabin
Copy link

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

@borstelmannl
Copy link

hey @alexrabin,
just replace your Browserrouter or any other router you have with the unstable_HistoryRouter. Then the example should work perfectly fine.

@bufke
Copy link

bufke commented Oct 14, 2022

Is there a way to use unstable_HistoryRouter with createBrowserRouter? The current react router tutorial uses createBrowserRouter.

@yashsharma04
Copy link

yashsharma04 commented Dec 21, 2022

@bufke did you find a way to solve this with createBrowserRouter ?

@bufke
Copy link

bufke commented Jan 16, 2023

I did not, I ended up using <BrowserRouter>.

@rmorse
Copy link
Author

rmorse commented Jan 22, 2023

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.

@ngocducdim
Copy link

Not woking with some gaming mouse have back button, any ideals?

@mgrahamjo
Copy link

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?'
  });
}

@Noah-JJ
Copy link

Noah-JJ commented Mar 13, 2023

thx!

@ikapta
Copy link

ikapta commented Mar 30, 2023

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! 💯

@LongTCH
Copy link

LongTCH commented Jul 9, 2023

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

@benlieb
Copy link

benlieb commented Aug 28, 2023

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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment