Skip to content

Instantly share code, notes, and snippets.

@MarksCode
Last active October 31, 2024 11:03
Show Gist options
  • Save MarksCode/64e438c82b0b2a1161e01c88ca0d0355 to your computer and use it in GitHub Desktop.
Save MarksCode/64e438c82b0b2a1161e01c88ca0d0355 to your computer and use it in GitHub Desktop.
return `usePrompt` capabilities from react-router
/**
* Prompts a user when they exit the page
*/
import { useCallback, useContext, useEffect } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
function useConfirmExit(confirmExit: () => boolean, when = true) {
const { navigator } = useContext(NavigationContext);
useEffect(() => {
if (!when) {
return;
}
const push = navigator.push;
navigator.push = (...args: Parameters<typeof push>) => {
const result = confirmExit();
if (result !== false) {
push(...args);
}
};
return () => {
navigator.push = push;
};
}, [navigator, confirmExit, when]);
}
export function usePrompt(message: string, when = true) {
useEffect(() => {
if (when) {
window.onbeforeunload = function () {
return message;
};
}
return () => {
window.onbeforeunload = null;
};
}, [message, when]);
const confirmExit = useCallback(() => {
const confirm = window.confirm(message);
return confirm;
}, [message]);
useConfirmExit(confirmExit, when);
}
@yara-tle
Copy link

yara-tle commented May 28, 2024

Hey @Niyatihd, would like to know how u made the useConfirmModal as well? @denchiklut your example wont work because can't use useBlocker with current router setup. Do you have any idea of using a custom modal with this example?

@denchiklut
Copy link

your example wont work because cant import unstable_useBlocker from the router.

@yara-tle yeah this example was written while it was unstable API. Now you can import useBlocker without unstable prefix

@dkovah
Copy link

dkovah commented Jul 16, 2024

@denchiklut useBlocker and usePrompt don't work with routers that don't have access to the new data API, like BrowserRouter component, this gist does

@saomartinho
Copy link

hey, do you manage to block when the user clicks the back browser button?

@kanavi57
Copy link

kanavi57 commented Oct 17, 2024

for anyone having issue with the back button, check this code

import { useCallback, useContext, useEffect } from "react";
import { useDispatch } from "react-redux";
import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";

function useConfirmExit(confirmExit: () => boolean, when = true) {
  const { navigator } = useContext(NavigationContext);

  useEffect(() => {
    if (!when) {
      return;
    }

    const push = navigator.push;

    navigator.push = (...args: Parameters<typeof push>) => {
      const result = confirmExit();

      if (result !== false) {
        push(...args);
      }
    };

    return () => {
      navigator.push = push;
    };
  }, [navigator, confirmExit, when]);
}

export function usePrompt(message: string, when = true) {
  useEffect(() => {
    if (!when) return;
    window.onbeforeunload = function () {
      return message;
    };

    window.history.pushState(null, "", window.location.href);
    window.onpopstate = function () {
      const confirm = window.confirm(message);

      if (confirm) {
        dispatch(setIsEditing(false));
        window.onpopstate = null;
        window.history.back();
      } else {
        window.history.pushState(null, "", window.location.href);
      }
    };
    return () => {
      window.onbeforeunload = null;
      window.onpopstate = null;
      }, [message, when]);

  const confirmExit = useCallback(() => {
    const confirm = window.confirm(message);

    if (confirm) {
      dispatch(setIsEditing(false));
    }

    return confirm;
  }, [message]);
  useConfirmExit(confirmExit, when);
}

@saomartinho
Copy link

for anyone having issue with the back button, check this code

import { useCallback, useContext, useEffect } from "react";
import { useDispatch } from "react-redux";
import { UNSAFE_NavigationContext as NavigationContext } from "react-router-dom";

function useConfirmExit(confirmExit: () => boolean, when = true) {
  const { navigator } = useContext(NavigationContext);

  useEffect(() => {
    if (!when) {
      return;
    }

    const push = navigator.push;

    navigator.push = (...args: Parameters<typeof push>) => {
      const result = confirmExit();

      if (result !== false) {
        push(...args);
      }
    };

    return () => {
      navigator.push = push;
    };
  }, [navigator, confirmExit, when]);
}

export function usePrompt(message: string, when = true) {
  useEffect(() => {
    if (!when) return;
    window.onbeforeunload = function () {
      return message;
    };

    window.history.pushState(null, "", window.location.href);
    window.onpopstate = function () {
      const confirm = window.confirm(message);

      if (confirm) {
        dispatch(setIsEditing(false));
        window.onpopstate = null;
        window.history.back();
      } else {
        window.history.pushState(null, "", window.location.href);
      }
    };
    return () => {
      window.onbeforeunload = null;
      window.onpopstate = null;
      }, [message, when]);

  const confirmExit = useCallback(() => {
    const confirm = window.confirm(message);

    if (confirm) {
      dispatch(setIsEditing(false));
    }

    return confirm;
  }, [message]);
  useConfirmExit(confirmExit, when);
}

Thanks for the suggestion, @kanavi57

This code works when the user clicks directly on the back button, but it won't work if the user long-presses the back button and chooses a specific page to return to.

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