Skip to content

feat(router-core,history): Global blocking status & proceed/reset actions - #4398

Closed
nizans wants to merge 1 commit into
TanStack:mainfrom
nizans:main
Closed

nizans wants to merge 1 commit into
TanStack:mainfrom
nizans:main

Conversation

@nizans

@nizans nizans commented Jun 12, 2025

Copy link
Copy Markdown

This PR introduces a global “blocker” state in and the callbacks needed to continue (proceed) or cancel (reset) a blocked navigation globally, to be consumed directly from the router state.
This enables controlling the blocking status out side the context of the useBlocker that was used to initiate the block.

The per-route useBlocker API remains unchanged—it now also updates the router-level state.

Discussed previously here.


This change adds BLOCK and DISMISS-BLOCK actions to the history notify function.
where the BLOCK action holds a ref to the current awaited promise resolver thats currently blocking the navigation, which is then consumed through the router state.


Why?

I needed a single, root-level modal that appears whenever any navigation is blocked (unsaved changes, etc.), instead of re-instantiating the modal wherever useBlocker is used.
I expected the router to expose some global blocking state, this PR attempts to provide this functionality.

A short example of the usage:

// Some component in some route
const SomeComponentAnywhere = () => {
  useBlocker({
    shouldBlockFn: () => true,
    withResolver: true,
  })
  
  return <div>something</div>
}

// Simple wrapper for consuming the blocking state
const useBlocking = () =>
  useRouterState({
    select: (state) => state.blocker,
  })

// Another component in some other route
const DifferentComponenetAnywhere = () => {
  const { proceed, reset, status } = useBlocking()

  return (
    <div>
      <span>The global blocking status: {status}</span>
      <button onClick={proceed}>Proceed the blocked navigation</button>
      <button onClick={reset}>Reset the blocked navigation</button>
    </div>
  )
}

A runnable demo is available in examples/global-blocking-state.

Any feedback is appreciated!

@Sheraff

Sheraff commented Jul 14, 2025

Copy link
Copy Markdown
Collaborator

Could you give a quick explanation for the switch from promises to async generators? I only quickly read the code, so maybe it should be obvious and I didn't see it.

@nizans

nizans commented Jul 17, 2025 •

Copy link
Copy Markdown
Author

Could you give a quick explanation for the switch from promises to async generators? I only quickly read the code, so maybe it should be obvious and I didn't see it.

Hi @Sheraff

The original blockerFn only lets the history "know" only after the blocking state is already resolved — meaning the navigation is no longer blocked (e.g. the user clicked the "dismiss" button).

To support global blocking state and functionality, there needs to be a blocking step in between, where the navigation is paused but not yet resolved.

To allow "history" to fire some kind of block events — and expose the ability to resolve that same awaited promise globally (i.e. provide the dismiss / proceed callbacks) — I needed a way to pass the resolver to the history layer as soon as the blocking starts.

There might be other ways to approach this, but I found async generators to be an effective solution for this case.

@nlynzaad

Copy link
Copy Markdown
Contributor

@nizans thank you for this. looking at the discord discussion, it seems there are an item or two that you still want to add on to this PR, maybe we should mark this as draft until those have been added in?

@nizans

nizans commented Jul 22, 2025 •

Copy link
Copy Markdown
Author

Thanks @nlynzaad

I’ve just pushed a commit that adds the proceedAll functionality that was discussed on Discord, so I believe this PR is good to go and ready for further review.

Quick explanation:
The proceedAll function is exposed through the global blocking state and allows proceeding all active blockers when multiple blockers are blocking navigation. This functionality is only available via the newly added global blocking state.

@nx-cloud

nx-cloud Bot commented Jul 31, 2025 •

Copy link
Copy Markdown
Contributor

View your CI Pipeline Execution ↗ for commit e8c83b359b200739461b41ca65dfe95b18008459


☁️ Nx Cloud last updated this comment at 2025-07-31 14:53:07 UTC

@nizans
nizans force-pushed the main branch 2 times, most recently from 0438630 to 990fcff Compare August 5, 2025 15:56
fix: standardize 'DISMISS_BLOCK' action naming in history module

clean empty line

proceedAll functionality and example

global blocking
@LadyBluenotes

Copy link
Copy Markdown
Member

Thanks for the work on this. The global blocking use case can still be still useful, but the implementation is now far behind main and needs a fresh design around the current history and framework contracts.

I’ve opened #7783 to track that work, including the browser-navigation and multi-blocker behavior. If you'd like to continue that work, it's welcomed!

@nizans

nizans commented Oct 6, 2026 •

Copy link
Copy Markdown
Author

@LadyBluenotes thanks

I reworked this around and created a new PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants