Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: usePreventNavigate #15

Open
wmertens opened this issue Sep 5, 2024 · 4 comments
Open

RFC: usePreventNavigate #15

wmertens opened this issue Sep 5, 2024 · 4 comments
Assignees

Comments

@wmertens
Copy link
Member

wmertens commented Sep 5, 2024

Proposed in #14

Champion

@wmertens

Summary:

usePreventNavigate((url?: URL) => boolean | Promise<boolean>)

What's the motivation for this proposal?

Problems you are trying to solve:

  • during filling in a form, waiting for a result etc, the user can inadvertently navigate away, and lose state

Goals you are trying to achieve:

  • block navigation when app dirty to prevent data loss
  • easy API

Proposed Solution / Feature

What do you propose?

We add usePreventNavigate(cb), which registers a handler that will be called when there is a navigation event. When the handler returns true, navigation is blocked.

This is actually not trivial to implement correctly because not all navigation passes through qwik-city, and when the browser is involved the answer need to be synchronous.

If the navigation attempt is going through qwik-city, the callback will receive the URL of the desired target. This makes it possible to perform the navigation later.

Code examples

using a modal library:

export default component$(() => {
  const okToNavigate = useSignal(true);
  usePreventNavigate$((url) => {
    if (!okToNavigate.value) {
      if (!url) return true;
      return confirmDialog(
        `Do you want to lose changes and go to ${navSig.value}?`
      ).then(answer => !answer);
    }
  });

  return (
    <div>
      <button onClick$={() => (okToNavigate.value = !okToNavigate.value)}>
        stuff
      </button>
      <br />
      more stuff
    </div>
  );
});

Using a separate modal:

export default component$(() => {
  const okToNavigate = useSignal(true);
  const navSig = useSignal<URL | number>();
  const showConfirm = useSignal(false);
  const nav = useNavigate();
  usePreventNavigate$((url) => {
    if (!okToNavigate.value) {
      if (url) {
        navSig.value = url;
        showConfirm.value = true;
      }
      return true;
    }
  });

  return (
    <div>
      <button onClick$={() => (okToNavigate.value = !okToNavigate.value)}>
        stuff
      </button>
      <br />
      more stuff
      <br />
      {showConfirm.value && (
        <div>
          <div>
            Do you want to lose changes and go to {String(navSig.value)}?
          </div>
          <button
            onClick$={() => {
              showConfirm.value = false;
              okToNavigate.value = true;
              nav(navSig.value!);
            }}
          >
            Yes
          </button>
          <button onClick$={() => (showConfirm.value = false)}>No</button>
        </div>
      )}
    </div>
  );
});

PRs/ Links / References

PRs:

QwikDev/qwik#6825

Reference:

Remix Router has useBlocker, there are interesting tidbits in their decisions document.

@github-actions github-actions bot added [STAGE-2] incomplete implementation Remove this label when implementation is complete [STAGE-2] not fully covered by tests yet Remove this label when tests are verified to cover the implementation [STAGE-2] unresolved discussions left Remove this label when all critical discussions are resolved on the issue [STAGE-3] docs changes not added yet Remove this label when the necessary documentation for the feature / change is added [STAGE-3] missing 2 reviews for RFC PRs Remove this label when at least 2 core team members reviewed and approved the RFC implementation labels Sep 5, 2024
@shairez shairez removed the [STAGE-2] incomplete implementation Remove this label when implementation is complete label Sep 5, 2024
@shairez shairez changed the title RFC: usePreventNavigate((url?: URL) => boolean | Promise<boolean>) RFC: usePreventNavigate Sep 5, 2024
@thejackshelton
Copy link
Member

So that I understand correctly, if the user was to navigate to this form page, and then fill out a form, let's say they hit the browser back button, this would prevent them from going to the previous page?

I sometimes do this on accident and see that the browser sometimes prevents me from going back to the previous URL and I'm relieved. I wonder at what point this would be a frustrating experience.

@wmertens wmertens added [STAGE-2] not fully covered by tests yet Remove this label when tests are verified to cover the implementation and removed [STAGE-2] not fully covered by tests yet Remove this label when tests are verified to cover the implementation [STAGE-3] docs changes not added yet Remove this label when the necessary documentation for the feature / change is added labels Sep 5, 2024
@wmertens
Copy link
Member Author

wmertens commented Sep 5, 2024

@thejackshelton yes correct. I hope it's not a frustrating experience when the navigation is only prevented when there are changes. By making it a hook, it can get at all the metadata it needs to make that decision.

@AnthonyPAlicea
Copy link

AnthonyPAlicea commented Sep 11, 2024

This is terrific functionality, and a common need in web apps when the page is in a dirty state. The UX way to avoid frustration is to ensure there is either:

  1. An explicit cancelation workflow when usePreventNavigate is used (like a Cancel button next to a Save button), or
  2. An auto-save feature, where preventing navigation happens when data has been changed and autosave hasn't yet triggered, but once auto-save triggers then navigation can occur.

These wouldn't need to be enforced in any way, but could be mentioned in documentation as best practices.

@shairez
Copy link
Contributor

shairez commented Sep 17, 2024

thanks @AnthonyPAlicea !
Important point!

Now that it's merged and will soon be released as "experimental" we would love a docs PR with your expertise sprinkled all over it 🙏🙌

@shairez shairez removed [STAGE-2] unresolved discussions left Remove this label when all critical discussions are resolved on the issue [STAGE-3] missing 2 reviews for RFC PRs Remove this label when at least 2 core team members reviewed and approved the RFC implementation labels Sep 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Developer Preview (STAGE 4)
Development

No branches or pull requests

4 participants