Skip to content

RFC: usePreventNavigate #15

@wmertens

Description

@wmertens

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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

Status

Developer Preview (STAGE 4)

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions