redwood icon indicating copy to clipboard operation
redwood copied to clipboard

WIP(router): Add useBlocker hook

Open xmaxcooking opened this issue 1 year ago • 1 comments

New version of #9820 based on @Tobbe 's input

Conceptually it would be used like this:

const PostForm = (props: PostFormProps) => {
  const form = useForm<FormPost>()
  const blocker = useBlocker(form.formState.isDirty)

  return (
    <div className="rw-form-wrapper">
      <Form formMethods={form} onSubmit={onSubmit} error={props.error}>
        State {blocker.state}
        {blocker.state === 'BLOCKED' ? (
          <div>
            <button type="button" onClick={() => blocker.confirm()}>
              Confirm
            </button>
            <button type="button" onClick={() => blocker.abort()}>
              Abort
            </button>
          </div>
        ) : null}
       ...
      </Form>
    </div>
   )
}

Help wanted

I'm currently stuck with the history.replaceState logic and I'm opening this PR just to see if someone has an idea what's happening here. Feel free to comment if you have a suggestion. I'd be happy to try it out.

Behaviour

The new navigation provider is patching the global history object to include the custom checks. If any blocker is true it shouldn't invoke the pushState and keep the current location but it somehow desyncs the active route from the URL. The blocker prevents the new page from being loaded but the window.location changes and on the next navigation it doesn't block anymore because it doesn't know it's on a page that should be blocked.

globalThis.history.pushState = function pushState(
  data: any,
  unused: string,
  url?: string | URL | null
) {
  const currentLocation = new URL(globalThis.location.href)
  const newLocation = new URL(globalThis.location.origin + url)

  if (
    currentLocation.pathname === newLocation.pathname &&
    currentLocation.search === newLocation.search &&
    currentLocation.hash === newLocation.hash
  ) {
    return
  }

  const blocked = blockers.some(({ check }) =>
    check(currentLocation, newLocation, NavigationMethod.PUSH, () => {
      originalPushState(data, unused, url)
    })
  )

  if (!blocked) {
    originalPushState(data, unused, url)
  }
}

xmaxcooking avatar Jan 17 '24 16:01 xmaxcooking

For anyone wondering about this PR. I haven't been able to get back to this but it's still on my list.

xmaxcooking avatar Feb 10 '24 09:02 xmaxcooking

Lots of work on this @xmaxcooking Thank you! Checking in to see if this is something you'd like us to keep open — fine on our end.

thedavidprice avatar Mar 26 '24 22:03 thedavidprice

Lots of work on this @xmaxcooking Thank you! Checking in to see if this is something you'd like us to keep open — fine on our end.

feel free to close it. the discussion with @Tobbe helped bring this into the right direction but at this point I'll open it again when I have a working state.

xmaxcooking avatar Mar 26 '24 22:03 xmaxcooking

Understood. We'd welcome you opening it up again in the future!

thedavidprice avatar Mar 27 '24 06:03 thedavidprice