Search code examples
javascriptreactjsnext.jsroutesnext-router

I want to create a confirmation modal before route changes in Next.js


I have code block like this

const onRouteChangeStart = React.useCallback(() => {
    if (formState.isDirty) {
      if (window.confirm('Confirmation message')) {
        return true;
      }
      NProgress.done();
      throw "Abort route change by user's confirmation.";
    }
  }, [formState.isDirty]);

  React.useEffect(() => {
    Router.events.on('routeChangeStart', onRouteChangeStart);

    return () => {
      Router.events.off('routeChangeStart', onRouteChangeStart);
    };
  }, [onRouteChangeStart]);

It works as I want but I want to add a Custom Confirmation Modal instead of Native Confirmation.

When I added, route changes did not stop. That's why I couldn't wait for the user response.

What can I do? Thank you for your responses.


Solution

  • There is a good sample here where it aborts the current route change and saves it to state, prompts the custom model. If confirmed, it pushes the route again.

    https://github.com/vercel/next.js/discussions/32231?sort=new?sort=new#discussioncomment-2033546

    import { useRouter } from 'next/router';
    import React from 'react';
    import Dialog from './Dialog';
    
    export interface UnsavedChangesDialogProps {
      shouldConfirmLeave: boolean;
    }
    
    export const UnsavedChangesDialog = ({
      shouldConfirmLeave,
    }: UnsavedChangesDialogProps): React.ReactElement<UnsavedChangesDialogProps> => {
      const [shouldShowLeaveConfirmDialog, setShouldShowLeaveConfirmDialog] = React.useState(false);
      const [nextRouterPath, setNextRouterPath] = React.useState<string>();
    
      const Router = useRouter();
    
      const onRouteChangeStart = React.useCallback(
        (nextPath: string) => {
          if (!shouldConfirmLeave) {
            return;
          }
    
          setShouldShowLeaveConfirmDialog(true);
          setNextRouterPath(nextPath);
    
          throw 'cancelRouteChange';
        },
        [shouldConfirmLeave]
      );
    
      const onRejectRouteChange = () => {
        setNextRouterPath(null);
        setShouldShowLeaveConfirmDialog(false);
      };
    
      const onConfirmRouteChange = () => {
        setShouldShowLeaveConfirmDialog(false);
        // simply remove the listener here so that it doesn't get triggered when we push the new route.
        // This assumes that the component will be removed anyway as the route changes
        removeListener();
        Router.push(nextRouterPath);
      };
    
      const removeListener = () => {
        Router.events.off('routeChangeStart', onRouteChangeStart);
      };
    
      React.useEffect(() => {
        Router.events.on('routeChangeStart', onRouteChangeStart);
    
        return removeListener;
      }, [onRouteChangeStart]);
    
      return (
        <Dialog
          title="You have unsaved changes"
          description="Leaving this page will discard unsaved changes. Are you sure?"
          confirmLabel="Discard changes"
          cancelLabel="Go back"
          isOpen={shouldShowLeaveConfirmDialog}
          onConfirm={onConfirmRouteChange}
          onReject={onRejectRouteChange}
        />
      );
    };