skip to Main Content

I am building SPA application. I have component with form changes to which could be either saved or canceled depending which button user pressed. I have a flag which indicates that some changes were made in this form.

I am using react-router-dom v6 for routing like this:

<Routes>
  <Route />
  <Route />
  <Route />
</Routes>

I want to show user a warning when he has unsaved changes and trying to navigate to another page (another route). It seems to me that there is no such a mechanic, I am also still not able to write a pure js script to solve this problem. beforeunload not working for me because it is only triggered on page refresh.

Is there any way I can achieve this?

2

Answers


  1. Chosen as BEST ANSWER

    This component accompanied by usage of the new createBrowserRouter react-router-dom api solved my problem. Anthough it is still not clear how to appropriately use it with query params, blocker seems not be working when I am changing them.

    import { useDidUpdate, useDisclosure } from '@mantine/hooks';
    import { memo, useCallback } from 'react';
    import { useTranslation } from 'react-i18next';
    import { unstable_BlockerFunction, useBlocker } from 'react-router-dom';
    import { WarningModal } from '../Modals/WarningModal/WarningModal';
    
    interface Props {
      shouldBlock: boolean | unstable_BlockerFunction;
    }
    
    const ChangesUnsavedBlocker = memo((props: Props) => {
      const { shouldBlock } = props;
    
      const { t } = useTranslation('common', {
        keyPrefix: 'changes_unsaved_blocker',
      });
    
      // https://reactrouter.com/en/6.21.1/hooks/use-blocker
      const blocker = useBlocker(shouldBlock);
    
      const [warningShown, { open: showWarning, close: hideWarning }] = useDisclosure(false);
    
      useDidUpdate(() => {
        if (blocker.state === 'blocked') {
          showWarning();
        } else {
          hideWarning();
        }
      }, [blocker.state]);
    
      const handleProceed = useCallback(() => {
        blocker.proceed?.();
    
        hideWarning();
      }, [blocker, hideWarning]);
    
      const handleReset = useCallback(() => {
        blocker.reset?.();
    
        hideWarning();
      }, [blocker, hideWarning]);
    
      return (
        <WarningModal
          width="416px"
          icon="warning"
          maxHeight="348px"
          title={t('title')}
          annotation={t('annotation')}
          approveTitle={t<string>('approve_title')}
          isOpened={warningShown}
          onClose={handleReset}
          onCancel={handleReset}
          onApprove={handleProceed}
        />
      );
    });
    
    export { ChangesUnsavedBlocker };
    

  2. Since React-Router v6 doesn’t have the Prompt component, you can create your own:

    import { unstable_useBlocker as useBlocker } from 'react-router-dom'
    
    function Prompt(props) {
        const block = props.when
        
        useBlocker(() => {
            if (block) {
                return ! window.confirm(props.message)
            }
            return false
        })
    
      return (
        <div key={block}/>
      )
    }
    
    export default Prompt
    

    Now, we can implement this in code:

    import { Prompt } from 'react-router'
    
    const MyComponent = () => (
      <>
        <Prompt
          when={shouldBlockNavigation}
          message='You have unsaved changes, are you sure you want to leave?'
        />
        {/* Component JSX */}
      </>
    )
    

    Source for custom Prompt component: Alternative for Prompt in React Router V6

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search