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>
);
};
@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