ItalyPaleAle / svelte-spa-router

Router for SPAs using Svelte 3
MIT License
1.53k stars 105 forks source link

Confirm before leaving page? #288

Open ariroffe opened 1 year ago

ariroffe commented 1 year ago

Hi, thank you for creating and maintaining this package!

I need to show a confirmation dialog before a user leaves a specific route, and call a function if she decides to leave. I can use window.onbeforeunload to handle cases where, for example, the user just navigates to a diferent site.

But beforeunload does not fire if the user manually enters a different route within the same spa (one that is still prefixed with #) in the address bar, or, e.g., if she presses the back button.

I've tried asking for confirmation in the route's associated component onDestroy method, but I couldn't find a way to stop the component's destruction afterwards. Even if I raise an error, the component remains on the page but the url still changes.

Is there any way of doing this with svelte-spa-router?

ItalyPaleAle commented 1 year ago

I believe you should be able to do that with a route pre-condition: https://github.com/ItalyPaleAle/svelte-spa-router/blob/master/Advanced%20Usage.md#route-pre-conditions

davidd7 commented 1 year ago

Hi, I'm also currently trying to implement a confirm dialog with pre-conditions. A problem I came across thereby is that an empty page is loaded when the pre-conditions fail. To circumvent this, I've tried to use on:conditionsFailed to manually set the location back to the original page (using replace or pop), but this leads to the component being reloaded, which is a problem in my use case. Would you happen to know if there's a way to stay on the same page and not reload it when a pre-condition fails?

ItalyPaleAle commented 1 year ago

I don't believe that's possible at this time. Perhaps it could be done by creating another hook that is executed before the route is un-mounted.

davidd7 commented 1 year ago

Thank you very much for your reply. You mean kinda like an exit-conditions counterpart to pre-conditions that would check registered conditions before leaving a route?

I can try to implement such a functionality analogously to how pre-conditions are implemented, if you think that that would be a good addition. :)

afreidz commented 1 year ago

plus one for this. looking to have a user confirm exiting with unsaved data before navigation. reloading the existing route on failure of a route guard would cause the form state to be lost. 😞 @davidd7 did you ever find a workaround?

afreidz commented 1 year ago

The workaround that I eventually landed on for now:

<script lang="ts">
  import { get } from "svelte/store";
  import { link, location } from "svelte-spa-router";
  import { confirmNavigation, dirty } from "@/lib/stores/ui";

  function checkLink(node: HTMLAnchorElement, params: any) {
    node.addEventListener("click", check);

    async function check(e: MouseEvent) {
      if (get(dirty)) {
        e.preventDefault();
        e.stopImmediatePropagation();
        const response = await new Promise((r) => {
          confirmNavigation.set(r);
        });
        confirmNavigation.set(undefined);
        if (response) {
          dirty.set(false);
          window.location.hash = node.getAttribute("href") || "";
        }
      }
    }

    const { update } = link(node, params);

    return {
      update,
      destroy() {
        node.removeEventListener("click", check);
      },
    };
  }
</script>

<a ... use:checkLink>
  <slot/>
</a>

This is a global link component with a use directive that bails out on certain conditions ... it runs the svelte-spa-router link directive to maintain functionality. It then sets a writable with the resolve method of a promise. if that promise resolves to true it tries to mimic the functionality of the link directive (which is a fancy way of saying that it updates the hash) save for the scroll linking which was not needed in my case. if it resolves to false it just clears the writable value and exits. elsewhere in the app, like a confirmation dialog, the writable can be called thusly:

$confirmNavigation?.(true | false);

Hope this helps someone looking for a workaround to this! I explored the option suggested above and realized that an exit route guard would be tricky because the hash updates independently of the route logic. so it could get messy figuring out if the outgoing route has exit conditions that fail and reverting the hash. This seems easier for now.

Other considerations: