Skip to content

Commit a96c71a

Browse files
committed
Add global blocking functionality to router
fix: standardize 'DISMISS_BLOCK' action naming in history module clean empty line proceedAll functionality and example global blocking
1 parent 0a3bb24 commit a96c71a

30 files changed

+894
-12
lines changed

e2e/react-router/basic-file-based/src/routeTree.gen.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { Route as StructuralSharingEnabledRouteImport } from './routes/structura
2727
import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default'
2828
import { Route as RedirectTargetRouteImport } from './routes/redirect/$target'
2929
import { Route as PostsPostIdRouteImport } from './routes/posts.$postId'
30+
import { Route as GlobalBlockerLayoutRouteImport } from './routes/global-blocker/_layout'
3031
import { Route as LayoutLayout2RouteImport } from './routes/_layout/_layout-2'
3132
import { Route as groupLazyinsideRouteImport } from './routes/(group)/lazyinside'
3233
import { Route as groupInsideRouteImport } from './routes/(group)/inside'
@@ -48,13 +49,20 @@ import { Route as ParamsPsWildcardSplatRouteImport } from './routes/params-ps/wi
4849
import { Route as ParamsPsNamedChar123fooChar125suffixRouteImport } from './routes/params-ps/named/{$foo}suffix'
4950
import { Route as ParamsPsNamedPrefixChar123fooChar125RouteImport } from './routes/params-ps/named/prefix{$foo}'
5051
import { Route as ParamsPsNamedFooRouteImport } from './routes/params-ps/named/$foo'
52+
import { Route as GlobalBlockerLayoutMultiBlockersRouteImport } from './routes/global-blocker/_layout.multi-blockers'
5153
import { Route as LayoutLayout2LayoutBRouteImport } from './routes/_layout/_layout-2/layout-b'
5254
import { Route as LayoutLayout2LayoutARouteImport } from './routes/_layout/_layout-2/layout-a'
5355
import { Route as groupSubfolderInsideRouteImport } from './routes/(group)/subfolder/inside'
5456
import { Route as groupLayoutInsidelayoutRouteImport } from './routes/(group)/_layout.insidelayout'
5557

58+
const GlobalBlockerRouteImport = createFileRoute('/global-blocker')()
5659
const groupRouteImport = createFileRoute('/(group)')()
5760

61+
const GlobalBlockerRoute = GlobalBlockerRouteImport.update({
62+
id: '/global-blocker',
63+
path: '/global-blocker',
64+
getParentRoute: () => rootRouteImport,
65+
} as any)
5866
const groupRoute = groupRouteImport.update({
5967
id: '/(group)',
6068
getParentRoute: () => rootRouteImport,
@@ -140,6 +148,10 @@ const PostsPostIdRoute = PostsPostIdRouteImport.update({
140148
path: '/$postId',
141149
getParentRoute: () => PostsRoute,
142150
} as any)
151+
const GlobalBlockerLayoutRoute = GlobalBlockerLayoutRouteImport.update({
152+
id: '/_layout',
153+
getParentRoute: () => GlobalBlockerRoute,
154+
} as any)
143155
const LayoutLayout2Route = LayoutLayout2RouteImport.update({
144156
id: '/_layout-2',
145157
getParentRoute: () => LayoutRoute,
@@ -251,6 +263,12 @@ const ParamsPsNamedFooRoute = ParamsPsNamedFooRouteImport.update({
251263
path: '/params-ps/named/$foo',
252264
getParentRoute: () => rootRouteImport,
253265
} as any)
266+
const GlobalBlockerLayoutMultiBlockersRoute =
267+
GlobalBlockerLayoutMultiBlockersRouteImport.update({
268+
id: '/multi-blockers',
269+
path: '/multi-blockers',
270+
getParentRoute: () => GlobalBlockerLayoutRoute,
271+
} as any)
254272
const LayoutLayout2LayoutBRoute = LayoutLayout2LayoutBRouteImport.update({
255273
id: '/layout-b',
256274
path: '/layout-b',
@@ -283,6 +301,7 @@ export interface FileRoutesByFullPath {
283301
'/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute
284302
'/inside': typeof groupInsideRoute
285303
'/lazyinside': typeof groupLazyinsideRoute
304+
'/global-blocker': typeof GlobalBlockerLayoutRouteWithChildren
286305
'/posts/$postId': typeof PostsPostIdRoute
287306
'/redirect/$target': typeof RedirectTargetRouteWithChildren
288307
'/search-params/default': typeof SearchParamsDefaultRoute
@@ -295,6 +314,7 @@ export interface FileRoutesByFullPath {
295314
'/subfolder/inside': typeof groupSubfolderInsideRoute
296315
'/layout-a': typeof LayoutLayout2LayoutARoute
297316
'/layout-b': typeof LayoutLayout2LayoutBRoute
317+
'/global-blocker/multi-blockers': typeof GlobalBlockerLayoutMultiBlockersRoute
298318
'/params-ps/named/$foo': typeof ParamsPsNamedFooRoute
299319
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
300320
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
@@ -321,6 +341,7 @@ export interface FileRoutesByTo {
321341
'/onlyrouteinside': typeof anotherGroupOnlyrouteinsideRoute
322342
'/inside': typeof groupInsideRoute
323343
'/lazyinside': typeof groupLazyinsideRoute
344+
'/global-blocker': typeof GlobalBlockerLayoutRouteWithChildren
324345
'/posts/$postId': typeof PostsPostIdRoute
325346
'/search-params/default': typeof SearchParamsDefaultRoute
326347
'/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute
@@ -332,6 +353,7 @@ export interface FileRoutesByTo {
332353
'/subfolder/inside': typeof groupSubfolderInsideRoute
333354
'/layout-a': typeof LayoutLayout2LayoutARoute
334355
'/layout-b': typeof LayoutLayout2LayoutBRoute
356+
'/global-blocker/multi-blockers': typeof GlobalBlockerLayoutMultiBlockersRoute
335357
'/params-ps/named/$foo': typeof ParamsPsNamedFooRoute
336358
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
337359
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
@@ -365,6 +387,8 @@ export interface FileRoutesById {
365387
'/(group)/inside': typeof groupInsideRoute
366388
'/(group)/lazyinside': typeof groupLazyinsideRoute
367389
'/_layout/_layout-2': typeof LayoutLayout2RouteWithChildren
390+
'/global-blocker': typeof GlobalBlockerRouteWithChildren
391+
'/global-blocker/_layout': typeof GlobalBlockerLayoutRouteWithChildren
368392
'/posts/$postId': typeof PostsPostIdRoute
369393
'/redirect/$target': typeof RedirectTargetRouteWithChildren
370394
'/search-params/default': typeof SearchParamsDefaultRoute
@@ -377,6 +401,7 @@ export interface FileRoutesById {
377401
'/(group)/subfolder/inside': typeof groupSubfolderInsideRoute
378402
'/_layout/_layout-2/layout-a': typeof LayoutLayout2LayoutARoute
379403
'/_layout/_layout-2/layout-b': typeof LayoutLayout2LayoutBRoute
404+
'/global-blocker/_layout/multi-blockers': typeof GlobalBlockerLayoutMultiBlockersRoute
380405
'/params-ps/named/$foo': typeof ParamsPsNamedFooRoute
381406
'/params-ps/named/prefix{$foo}': typeof ParamsPsNamedPrefixChar123fooChar125Route
382407
'/params-ps/named/{$foo}suffix': typeof ParamsPsNamedChar123fooChar125suffixRoute
@@ -407,6 +432,7 @@ export interface FileRouteTypes {
407432
| '/onlyrouteinside'
408433
| '/inside'
409434
| '/lazyinside'
435+
| '/global-blocker'
410436
| '/posts/$postId'
411437
| '/redirect/$target'
412438
| '/search-params/default'
@@ -419,6 +445,7 @@ export interface FileRouteTypes {
419445
| '/subfolder/inside'
420446
| '/layout-a'
421447
| '/layout-b'
448+
| '/global-blocker/multi-blockers'
422449
| '/params-ps/named/$foo'
423450
| '/params-ps/named/prefix{$foo}'
424451
| '/params-ps/named/{$foo}suffix'
@@ -445,6 +472,7 @@ export interface FileRouteTypes {
445472
| '/onlyrouteinside'
446473
| '/inside'
447474
| '/lazyinside'
475+
| '/global-blocker'
448476
| '/posts/$postId'
449477
| '/search-params/default'
450478
| '/structural-sharing/$enabled'
@@ -456,6 +484,7 @@ export interface FileRouteTypes {
456484
| '/subfolder/inside'
457485
| '/layout-a'
458486
| '/layout-b'
487+
| '/global-blocker/multi-blockers'
459488
| '/params-ps/named/$foo'
460489
| '/params-ps/named/prefix{$foo}'
461490
| '/params-ps/named/{$foo}suffix'
@@ -488,6 +517,8 @@ export interface FileRouteTypes {
488517
| '/(group)/inside'
489518
| '/(group)/lazyinside'
490519
| '/_layout/_layout-2'
520+
| '/global-blocker'
521+
| '/global-blocker/_layout'
491522
| '/posts/$postId'
492523
| '/redirect/$target'
493524
| '/search-params/default'
@@ -500,6 +531,7 @@ export interface FileRouteTypes {
500531
| '/(group)/subfolder/inside'
501532
| '/_layout/_layout-2/layout-a'
502533
| '/_layout/_layout-2/layout-b'
534+
| '/global-blocker/_layout/multi-blockers'
503535
| '/params-ps/named/$foo'
504536
| '/params-ps/named/prefix{$foo}'
505537
| '/params-ps/named/{$foo}suffix'
@@ -529,6 +561,7 @@ export interface RootRouteChildren {
529561
Char45824Char54620Char48124Char44397Route: typeof Char45824Char54620Char48124Char44397Route
530562
anotherGroupOnlyrouteinsideRoute: typeof anotherGroupOnlyrouteinsideRoute
531563
groupRoute: typeof groupRouteWithChildren
564+
GlobalBlockerRoute: typeof GlobalBlockerRouteWithChildren
532565
RedirectTargetRoute: typeof RedirectTargetRouteWithChildren
533566
StructuralSharingEnabledRoute: typeof StructuralSharingEnabledRoute
534567
ParamsPsIndexRoute: typeof ParamsPsIndexRoute
@@ -550,6 +583,13 @@ export interface RootRouteChildren {
550583

551584
declare module '@tanstack/react-router' {
552585
interface FileRoutesByPath {
586+
'/global-blocker': {
587+
id: '/global-blocker'
588+
path: '/global-blocker'
589+
fullPath: '/global-blocker'
590+
preLoaderRoute: typeof GlobalBlockerRouteImport
591+
parentRoute: typeof rootRouteImport
592+
}
553593
'/(group)': {
554594
id: '/(group)'
555595
path: '/'
@@ -669,6 +709,13 @@ declare module '@tanstack/react-router' {
669709
preLoaderRoute: typeof PostsPostIdRouteImport
670710
parentRoute: typeof PostsRoute
671711
}
712+
'/global-blocker/_layout': {
713+
id: '/global-blocker/_layout'
714+
path: '/global-blocker'
715+
fullPath: '/global-blocker'
716+
preLoaderRoute: typeof GlobalBlockerLayoutRouteImport
717+
parentRoute: typeof GlobalBlockerRoute
718+
}
672719
'/_layout/_layout-2': {
673720
id: '/_layout/_layout-2'
674721
path: ''
@@ -816,6 +863,13 @@ declare module '@tanstack/react-router' {
816863
preLoaderRoute: typeof ParamsPsNamedFooRouteImport
817864
parentRoute: typeof rootRouteImport
818865
}
866+
'/global-blocker/_layout/multi-blockers': {
867+
id: '/global-blocker/_layout/multi-blockers'
868+
path: '/multi-blockers'
869+
fullPath: '/global-blocker/multi-blockers'
870+
preLoaderRoute: typeof GlobalBlockerLayoutMultiBlockersRouteImport
871+
parentRoute: typeof GlobalBlockerLayoutRoute
872+
}
819873
'/_layout/_layout-2/layout-b': {
820874
id: '/_layout/_layout-2/layout-b'
821875
path: '/layout-b'
@@ -925,6 +979,29 @@ const groupRouteChildren: groupRouteChildren = {
925979

926980
const groupRouteWithChildren = groupRoute._addFileChildren(groupRouteChildren)
927981

982+
interface GlobalBlockerLayoutRouteChildren {
983+
GlobalBlockerLayoutMultiBlockersRoute: typeof GlobalBlockerLayoutMultiBlockersRoute
984+
}
985+
986+
const GlobalBlockerLayoutRouteChildren: GlobalBlockerLayoutRouteChildren = {
987+
GlobalBlockerLayoutMultiBlockersRoute: GlobalBlockerLayoutMultiBlockersRoute,
988+
}
989+
990+
const GlobalBlockerLayoutRouteWithChildren =
991+
GlobalBlockerLayoutRoute._addFileChildren(GlobalBlockerLayoutRouteChildren)
992+
993+
interface GlobalBlockerRouteChildren {
994+
GlobalBlockerLayoutRoute: typeof GlobalBlockerLayoutRouteWithChildren
995+
}
996+
997+
const GlobalBlockerRouteChildren: GlobalBlockerRouteChildren = {
998+
GlobalBlockerLayoutRoute: GlobalBlockerLayoutRouteWithChildren,
999+
}
1000+
1001+
const GlobalBlockerRouteWithChildren = GlobalBlockerRoute._addFileChildren(
1002+
GlobalBlockerRouteChildren,
1003+
)
1004+
9281005
interface RedirectTargetRouteChildren {
9291006
RedirectTargetViaBeforeLoadRoute: typeof RedirectTargetViaBeforeLoadRoute
9301007
RedirectTargetViaLoaderRoute: typeof RedirectTargetViaLoaderRoute
@@ -953,6 +1030,7 @@ const rootRouteChildren: RootRouteChildren = {
9531030
Char45824Char54620Char48124Char44397Route,
9541031
anotherGroupOnlyrouteinsideRoute: anotherGroupOnlyrouteinsideRoute,
9551032
groupRoute: groupRouteWithChildren,
1033+
GlobalBlockerRoute: GlobalBlockerRouteWithChildren,
9561034
RedirectTargetRoute: RedirectTargetRouteWithChildren,
9571035
StructuralSharingEnabledRoute: StructuralSharingEnabledRoute,
9581036
ParamsPsIndexRoute: ParamsPsIndexRoute,

e2e/react-router/basic-file-based/src/routes/__root.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ function RootComponent() {
131131
>
132132
This Route Does Not Exist
133133
</Link>
134+
<Link
135+
to="/global-blocker/multi-blockers"
136+
activeProps={{
137+
className: 'font-bold',
138+
}}
139+
>
140+
Multi Blockers
141+
</Link>
134142
</div>
135143
<hr />
136144
<Outlet />
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import * as React from 'react'
2+
import { createFileRoute, useBlocker } from '@tanstack/react-router'
3+
4+
export const Route = createFileRoute('/global-blocker/_layout/multi-blockers')({
5+
component: MultiBlockersPage,
6+
})
7+
8+
function MultiBlockersPage() {
9+
const blocker1 = useBlocker({
10+
shouldBlockFn: async () => Promise.resolve(true),
11+
enableBeforeUnload: true,
12+
disabled: false,
13+
withResolver: true,
14+
})
15+
16+
const blocker2 = useBlocker({
17+
shouldBlockFn: async () => Promise.resolve(true),
18+
enableBeforeUnload: true,
19+
disabled: false,
20+
withResolver: true,
21+
})
22+
23+
return (
24+
<div>
25+
<h1>This page always blocks navigation</h1>
26+
<div data-testid="blocker-1-status">blocker1 is {blocker1.status}</div>
27+
<div data-testid="blocker-2-status">blocker2 is {blocker2.status}</div>
28+
</div>
29+
)
30+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
Outlet,
3+
createFileRoute,
4+
useNavigationBlockingState,
5+
} from '@tanstack/react-router'
6+
7+
export const Route = createFileRoute('/global-blocker/_layout')({
8+
component: RouteComponent,
9+
})
10+
11+
function RouteComponent() {
12+
const { proceed, reset, status, proceedAll } = useNavigationBlockingState()
13+
14+
return (
15+
<div>
16+
{status === 'blocked' ? (
17+
<div
18+
style={{
19+
marginLeft: 'auto',
20+
paddingRight: '240px',
21+
position: 'fixed',
22+
border: '1px solid black',
23+
width: '600px',
24+
height: '600px',
25+
left: '50px',
26+
bottom: '50px',
27+
display: 'flex',
28+
alignItems: 'center',
29+
justifyContent: 'center',
30+
flexDirection: 'column',
31+
gap: '10px',
32+
fontSize: '20px',
33+
color: 'white',
34+
borderRadius: '10px',
35+
boxShadow: '0 0 10px rgba(0,0,0,0.5)',
36+
zIndex: 1000,
37+
backgroundColor: 'saddlebrown',
38+
}}
39+
>
40+
<h3>Global Blocking Modal</h3>
41+
<div>Navigation is blocked</div>
42+
<div className="flex gap-2 flex-col">
43+
<button onClick={proceed}>Proceed</button>
44+
<button onClick={proceedAll}>Proceed All</button>
45+
<button onClick={reset}>Reset</button>
46+
</div>
47+
</div>
48+
) : null}
49+
<Outlet />
50+
</div>
51+
)
52+
}

0 commit comments

Comments
 (0)