TanStack / TanStack/router

`beforeunload` dialog triggered by redirect to external site even after original blocker proceed occurs

Open
#4,094 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Which project does this relate to?

Router

Describe the bug

There is a bug with the useBlocker hook where under specific conditions, both a custom UI blocker AND the browser's native beforeunload confirmation dialog appear in sequence. The custom UI blocker appears first, and then after calling proceed, the browser's native confirmation dialog also appears.

The issue occurs when all of the following conditions are met:

  1. withResolver is set to true (to enable the custom UI)
  2. Both enableBeforeUnload and shouldBlockFn return true until the hook is fully unmounted (in our case, based on form dirtiness detection)
  3. In the beforeLoad of the destination route, there is a throw redirect to an external site

Additionally, passing ignoreBlocker: true to the redirect function that is thrown does not prevent the browser confirmation dialog from showing.

Your Example Website or App

https://stackblitz.com/edit/vitejs-vite-hyxmxvkd?file=src%2Froutes%2Findex.tsx

Steps to Reproduce the Bug or Issue
  1. Create a component that uses useBlocker with:

    // In src/routes/index.tsx
    const { status, proceed, reset } = useBlocker({
      shouldBlockFn: () => true,
      enableBeforeUnload: () => true,
      withResolver: true,
    });
    
  2. Add a route with a beforeLoad function that contains a redirect to an external site:

    // In src/routes/external.tsx
    export const Route = createFileRoute('/external')({
      component: External,
      beforeLoad: () => {
        throw redirect({ href: 'https://example.com/' });
      },
    });
    
  3. Try to navigate to the '/external' route (you can add a link to it on the index page)

Expected behavior

When navigating to a route that redirects externally, only the custom UI blocker should appear. Once the user confirms by clicking "proceed" in the custom UI, the navigation should continue without triggering the browser's native beforeunload confirmation dialog.

Screenshots or Videos

No response

Platform
  • OS: macOS
  • Browser: Chrome
  • Version: 1.119.0
Additional context

I found a workaround by modifying the enableBeforeUnload function to include a check against the router state:

enableBeforeUnload: () => formIsDirty && router.state.status !== "pending"

This prevents the browser confirmation dialog from appearing when the router status is "pending", which occurs during the redirect. However, this seems like a workaround for what appears to be a bug in the interaction between useBlocker, beforeunload events, and redirects to external sites.

The key reproduction components are:

  1. A route using useBlocker with both shouldBlockFn and enableBeforeUnload returning true
  2. A separate route with a beforeLoad function that throws an external redirect
  3. When navigating to the external route, first the custom UI blocking appears, and after clicking "proceed", a browser confirmation dialog also appears

It would be helpful if there was a more official way to handle this case, perhaps by having ignoreBlocker: true properly bypass all forms of blocking, including the beforeunload event.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the reproduction in src/routes/index.tsx and src/routes/external.tsx, then trace useBlocker, beforeLoad, redirect, and beforeunload handling in the Router implementation. Reproduce the external redirect in the linked StackBlitz example and verify that proceeding through the custom blocker does not trigger a second native confirmation dialog.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.