clear-react-router 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -18
- package/dist/components/Router.d.ts +1 -1
- package/dist/config/routerConfig.d.ts +1 -0
- package/dist/hooks/useAction.d.ts +1 -6
- package/dist/hooks/useNavigate.d.ts +1 -2
- package/dist/index.js +184 -163
- package/dist/runtime/invalidate.d.ts +1 -4
- package/dist/runtime/navigate.d.ts +1 -1
- package/dist/types.d.ts +21 -1
- package/dist/utils/revalidateCache.d.ts +1 -1
- package/package.json +1 -1
- package/dist/hooks/useGetAction.d.ts +0 -4
package/README.md
CHANGED
|
@@ -38,23 +38,6 @@ It provides first-class support for:
|
|
|
38
38
|
|
|
39
39
|
## API
|
|
40
40
|
|
|
41
|
-
### `createRouter(routes)`
|
|
42
|
-
|
|
43
|
-
Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic params, builds nested paths.
|
|
44
|
-
|
|
45
|
-
| Property | Type | Description |
|
|
46
|
-
|----------|------|-------------|
|
|
47
|
-
| `path` | `string` | Route path, e.g., `/user/:userId` |
|
|
48
|
-
| `element` | `ReactElement \| () => ReactElement \| LazyComponent` | Component to render |
|
|
49
|
-
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | Auth checks and redirects. Can update context via `setContext`. `redirect` is provided by the router |
|
|
50
|
-
| `loader` | `({ params, context, setContext }) => Promise<unknown>` | Fetch data using route params and context. Can update context via `setContext` |
|
|
51
|
-
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | Analytics, side effects after data is loaded. Can update context via `setContext` |
|
|
52
|
-
| `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
|
|
53
|
-
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback for the route's `loader`. Overrides the global `defaultLoaderFallback` set in `Router` |
|
|
54
|
-
| `errorElement` | `ReactElement \| () => ReactElement` | Error fallback for the route. Overrides the global `defaultErrorElement` set in `Router` |
|
|
55
|
-
| `staleTime` | `number` | Time in ms before cached data is considered stale and re-fetched in the background. If not provided, data never expires (cached forever) |
|
|
56
|
-
| `actions` | `({ params, context, invalidate, setContext }) => Record<string, (formData: FormData) => unknown \| Promise<unknown>>` | Defines route actions for data mutations. Actions receive `FormData`, can update context via `setContext`, and can refresh loader data using the router-provided `invalidate`. |
|
|
57
|
-
|
|
58
41
|
### `Router`
|
|
59
42
|
|
|
60
43
|
| Prop | Type | Default | Description |
|
|
@@ -64,6 +47,7 @@ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic pa
|
|
|
64
47
|
| `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
|
|
65
48
|
| `defaultLoaderFallback` | `ReactElement \| () => ReactElement` | `optional` | Default loading fallback for every route loader |
|
|
66
49
|
| `defaultErrorElement` | `ReactElement \| () => ReactElement` | `optional` | Default error fallback for every route |
|
|
50
|
+
| `defaultRetry` | `number \| { count: number; delay: number }` | `optional` | Default cache revalidation retry policy for all routes |
|
|
67
51
|
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | `undefined` | Runs before every navigation. Useful for authentication, analytics, or updating shared context. |
|
|
68
52
|
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | `undefined` | Runs after every successful navigation. Useful for analytics, page tracking, or other global side effects. |
|
|
69
53
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
@@ -82,9 +66,26 @@ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic pa
|
|
|
82
66
|
<Router routes={routes} spinner={false} isAnimated /> {/* disable the spinner */}
|
|
83
67
|
</div>
|
|
84
68
|
```
|
|
85
|
-
|
|
86
69
|
> **Note:** When `isAnimated` is enabled, `loaderFallback` is not shown. Instead, a small spinner appears (if `spinner={true}`). On the initial page load, however, the route's loaderFallback is rendered if available.
|
|
87
70
|
|
|
71
|
+
### `createRouter(routes)`
|
|
72
|
+
|
|
73
|
+
Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic params, builds nested paths.
|
|
74
|
+
|
|
75
|
+
| Property | Type | Description |
|
|
76
|
+
|----------|------|-------------|
|
|
77
|
+
| `path` | `string` | Route path, e.g., `/user/:userId` |
|
|
78
|
+
| `element` | `ReactElement \| () => ReactElement \| LazyComponent` | Component to render |
|
|
79
|
+
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | Auth checks and redirects. Can update context via `setContext`. `redirect` is provided by the router |
|
|
80
|
+
| `loader` | `({ params, context, setContext }) => Promise<unknown>` | Fetch data using route params and context. Can update context via `setContext` |
|
|
81
|
+
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | Analytics, side effects after data is loaded. Can update context via `setContext` |
|
|
82
|
+
| `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
|
|
83
|
+
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback for the route's `loader`. Overrides the global `defaultLoaderFallback` set in `Router` |
|
|
84
|
+
| `retry` | `number \| { count: number; delay: number }` | `optional` | Overrides the global cache revalidation retry policy for this route |
|
|
85
|
+
| `errorElement` | `ReactElement \| () => ReactElement` | Error fallback for the route. Overrides the global `defaultErrorElement` set in `Router` |
|
|
86
|
+
| `staleTime` | `number` | Time in ms before cached data is considered stale and re-fetched in the background. If not provided, data never expires (cached forever) |
|
|
87
|
+
| `actions` | `({ params, context, invalidate, setContext }) => Record<string, (formData: FormData) => unknown \| Promise<unknown>>` | Defines route actions for data mutations. Actions receive `FormData`, can update context via `setContext`, and can refresh loader data using the router-provided `invalidate`. |
|
|
88
|
+
|
|
88
89
|
### `Link`
|
|
89
90
|
|
|
90
91
|
Component for client-side navigation with prefetch support.
|
|
@@ -125,6 +126,68 @@ import { Router, Link } from 'clear-react-router';
|
|
|
125
126
|
```
|
|
126
127
|
**Important**: prefetch="render" should be used sparingly, as it preloads data immediately when the link is rendered, which may cause unnecessary network requests.
|
|
127
128
|
|
|
129
|
+
## Retry
|
|
130
|
+
|
|
131
|
+
Sometimes a request may fail because of a temporary network issue or a short-lived server problem. Instead of immediately rendering the error state, you can configure the router to automatically retry loading route data.
|
|
132
|
+
|
|
133
|
+
### Route-level retry
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
{
|
|
137
|
+
path: '/posts',
|
|
138
|
+
loader: loadPosts,
|
|
139
|
+
retry: 3,
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`retry: 3` means the router will make up to **3 additional attempts** after the initial failed request (up to **4 attempts** in total).
|
|
144
|
+
|
|
145
|
+
You can also specify a delay between attempts:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
{
|
|
149
|
+
path: '/posts',
|
|
150
|
+
loader: loadPosts,
|
|
151
|
+
retry: {
|
|
152
|
+
count: 3,
|
|
153
|
+
delay: 500,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Global retry
|
|
159
|
+
|
|
160
|
+
To apply the same retry policy to all routes, use `defaultRetry`:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
<Router routes={routes} defaultRetry={2} />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
or with a delay:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
<Router routes={routes} defaultRetry={{ count: 2, delay: 500 }} />
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
A route-level `retry` always overrides `defaultRetry`.
|
|
173
|
+
|
|
174
|
+
### How it works
|
|
175
|
+
|
|
176
|
+
Unlike many routing libraries, retry is **not limited to the initial loader execution**.
|
|
177
|
+
|
|
178
|
+
The retry policy is applied to the router's **cache revalidation mechanism**, so it automatically works for every operation that reloads route data, including:
|
|
179
|
+
|
|
180
|
+
* Initial route loading
|
|
181
|
+
* Cache revalidation
|
|
182
|
+
* `invalidate()`
|
|
183
|
+
* `prefetch()`
|
|
184
|
+
|
|
185
|
+
This ensures consistent behavior regardless of how the data is being refreshed.
|
|
186
|
+
|
|
187
|
+
### Why?
|
|
188
|
+
|
|
189
|
+
The router treats the route loader as the single source of truth for route data. Since every data refresh goes through the same cache revalidation pipeline, retry is configured once and automatically applies everywhere without any additional code.
|
|
190
|
+
|
|
128
191
|
### `redirect`
|
|
129
192
|
|
|
130
193
|
Function provided to `beforeLoad` for programmatic redirection.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types';
|
|
2
|
-
export declare const Router: ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, context: initialContext, defaultLoaderFallback, defaultErrorElement, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
2
|
+
export declare const Router: ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, context: initialContext, defaultLoaderFallback, defaultErrorElement, defaultRetry, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -7,6 +7,7 @@ declare class RouterConfig {
|
|
|
7
7
|
hoverPrefetchDelay: number;
|
|
8
8
|
beforeLoad?: ClientRouteItem['beforeLoad'];
|
|
9
9
|
afterLoad?: ClientRouteItem['afterLoad'];
|
|
10
|
+
defaultRetry?: RouterProps['defaultRetry'];
|
|
10
11
|
configure(config: Partial<RouterConfig>): void;
|
|
11
12
|
}
|
|
12
13
|
export declare const routerConfig: RouterConfig;
|
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
2
|
-
onSuccess: (args: unknown) => void;
|
|
3
|
-
onError: (args: unknown) => void;
|
|
4
|
-
}> | undefined;
|
|
5
|
-
export declare const useAction: (action: string, options?: Options) => (formData: FormData) => Promise<void>;
|
|
6
|
-
export {};
|
|
1
|
+
export declare const useAction: (action: string, options?: import("../types").Options) => (arg: FormData) => Promise<void>;
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const useNavigate: () => (arg: Location | string | -1) => Promise<void>;
|
|
1
|
+
export declare const useNavigate: () => (arg: import("..").Location | string | -1) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -95,6 +95,7 @@ var RouterConfig = class {
|
|
|
95
95
|
_defineProperty(this, "hoverPrefetchDelay", 150);
|
|
96
96
|
_defineProperty(this, "beforeLoad", void 0);
|
|
97
97
|
_defineProperty(this, "afterLoad", void 0);
|
|
98
|
+
_defineProperty(this, "defaultRetry", void 0);
|
|
98
99
|
}
|
|
99
100
|
configure(config) {
|
|
100
101
|
Object.assign(this, config);
|
|
@@ -227,9 +228,9 @@ var findRoute = (pathname, includeAll) => {
|
|
|
227
228
|
//#endregion
|
|
228
229
|
//#region runtime/navigate.ts
|
|
229
230
|
var navigationSeq = 0;
|
|
230
|
-
var createNavigate = (
|
|
231
|
-
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap } =
|
|
232
|
-
const commitNavigation = createCommitNavigation(createCommitState(
|
|
231
|
+
var createNavigate = (routerState, revalidateCache) => {
|
|
232
|
+
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap } = routerState;
|
|
233
|
+
const commitNavigation = createCommitNavigation(createCommitState(routerState), prevPathnameRef);
|
|
233
234
|
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
234
235
|
const getContext = () => ({
|
|
235
236
|
context: contextState.getState(),
|
|
@@ -367,12 +368,11 @@ var createInvalidate = ({ routeItemDataState, loaderStateRef, timestampMap, curr
|
|
|
367
368
|
await Promise.all(childPathList.map((el) => invalidateItem(`${pathname}${el}`, true)));
|
|
368
369
|
}
|
|
369
370
|
};
|
|
370
|
-
async
|
|
371
|
+
return async (pathList, options) => {
|
|
371
372
|
const routePathname = routeItemDataState.getState().location.pathname;
|
|
372
373
|
const pathnameList = Array.isArray(pathList) ? pathList : pathList ? [pathList] : [routePathname];
|
|
373
374
|
await Promise.all(pathnameList.map((pathname) => invalidateItem(pathname, options?.withChildren)));
|
|
374
|
-
}
|
|
375
|
-
return invalidate;
|
|
375
|
+
};
|
|
376
376
|
};
|
|
377
377
|
//#endregion
|
|
378
378
|
//#region runtime/prefetch.ts
|
|
@@ -387,51 +387,80 @@ var createPrefetch = (revalidateCache) => async (pathname) => {
|
|
|
387
387
|
//#region utils/revalidateCache.ts
|
|
388
388
|
var loaderMapRef = {};
|
|
389
389
|
var loadingPromises = /* @__PURE__ */ new Map();
|
|
390
|
-
var
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
390
|
+
var isObjectRetry = (arg) => typeof arg === "object";
|
|
391
|
+
var createRetry = (arg) => {
|
|
392
|
+
if (arg === void 0) return null;
|
|
393
|
+
return {
|
|
394
|
+
count: isObjectRetry(arg) ? arg.count : arg,
|
|
395
|
+
delay: isObjectRetry(arg) ? arg.delay : 0
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
var getRetry = (routeItem) => {
|
|
399
|
+
const routeRetry = createRetry(routeItem?.retry);
|
|
400
|
+
const globalRetry = createRetry(routerConfig.defaultRetry);
|
|
401
|
+
if (!routeRetry && !globalRetry) return null;
|
|
402
|
+
return {
|
|
403
|
+
count: routeRetry ? routeRetry.count : globalRetry?.count || 0,
|
|
404
|
+
delay: routeRetry ? routeRetry.delay : globalRetry?.delay || 0
|
|
405
|
+
};
|
|
406
|
+
};
|
|
407
|
+
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
408
|
+
var createRevalidateCache = (routerState) => {
|
|
409
|
+
const revalidateCache = async ({ routeItem, pathname }, retried = 0) => {
|
|
403
410
|
if (!routeItem?.loader) return;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
context,
|
|
414
|
-
setContext
|
|
415
|
-
});
|
|
416
|
-
timestampMap.set(pathname, Date.now());
|
|
417
|
-
loaderStateRef.set((prev) => ({
|
|
418
|
-
...prev,
|
|
419
|
-
data: result,
|
|
420
|
-
loaderError: null
|
|
421
|
-
}));
|
|
422
|
-
loaderMapRef[pathname] = loaderStateRef.value;
|
|
423
|
-
} catch (error) {
|
|
424
|
-
loaderStateRef.set((prev) => ({
|
|
425
|
-
...prev,
|
|
426
|
-
data: null,
|
|
427
|
-
loaderError: error
|
|
428
|
-
}));
|
|
429
|
-
} finally {
|
|
430
|
-
loadingPromises.delete(pathname);
|
|
411
|
+
const isCacheItemFresh = createIsCacheItemFresh(routerState.timestampMap);
|
|
412
|
+
const { loaderStateRef, timestampMap, contextState } = routerState;
|
|
413
|
+
if (loadingPromises.has(pathname)) return loadingPromises.get(pathname);
|
|
414
|
+
if (isCacheItemFresh({
|
|
415
|
+
routeItem,
|
|
416
|
+
pathname
|
|
417
|
+
})) {
|
|
418
|
+
loaderStateRef.set(loaderMapRef[pathname]);
|
|
419
|
+
return;
|
|
431
420
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
421
|
+
const promise = (async () => {
|
|
422
|
+
if (!routeItem?.loader) return;
|
|
423
|
+
try {
|
|
424
|
+
const context = contextState.getState();
|
|
425
|
+
const setContext = contextState.setState;
|
|
426
|
+
const params = getParamsObject({
|
|
427
|
+
params: routeItem.params,
|
|
428
|
+
pathname
|
|
429
|
+
});
|
|
430
|
+
const result = await routeItem?.loader({
|
|
431
|
+
params,
|
|
432
|
+
context,
|
|
433
|
+
setContext
|
|
434
|
+
});
|
|
435
|
+
timestampMap.set(pathname, Date.now());
|
|
436
|
+
loaderStateRef.set((prev) => ({
|
|
437
|
+
...prev,
|
|
438
|
+
data: result,
|
|
439
|
+
loaderError: null
|
|
440
|
+
}));
|
|
441
|
+
loaderMapRef[pathname] = loaderStateRef.value;
|
|
442
|
+
} catch (error) {
|
|
443
|
+
const retry = getRetry(routeItem);
|
|
444
|
+
if (retry && retry.count > retried) {
|
|
445
|
+
loadingPromises.delete(pathname);
|
|
446
|
+
if (retry.delay) await sleep(retry.delay);
|
|
447
|
+
await revalidateCache({
|
|
448
|
+
routeItem,
|
|
449
|
+
pathname
|
|
450
|
+
}, retried + 1);
|
|
451
|
+
} else loaderStateRef.set((prev) => ({
|
|
452
|
+
...prev,
|
|
453
|
+
data: null,
|
|
454
|
+
loaderError: error
|
|
455
|
+
}));
|
|
456
|
+
} finally {
|
|
457
|
+
loadingPromises.delete(pathname);
|
|
458
|
+
}
|
|
459
|
+
})();
|
|
460
|
+
loadingPromises.set(pathname, promise);
|
|
461
|
+
return promise;
|
|
462
|
+
};
|
|
463
|
+
return revalidateCache;
|
|
435
464
|
};
|
|
436
465
|
//#endregion
|
|
437
466
|
//#region cell.ts
|
|
@@ -471,6 +500,31 @@ var createRouterInstance = () => {
|
|
|
471
500
|
timestampMap: /* @__PURE__ */ new Map()
|
|
472
501
|
};
|
|
473
502
|
const revalidateCache = createRevalidateCache(routerState);
|
|
503
|
+
const navigate = createNavigate(routerState, revalidateCache);
|
|
504
|
+
const invalidate = createInvalidate(routerState, revalidateCache);
|
|
505
|
+
const prefetch = createPrefetch(revalidateCache);
|
|
506
|
+
const useGetAction = (actionKey) => {
|
|
507
|
+
const { routeItem, location } = routerState.routeItemDataState.getState();
|
|
508
|
+
const context = routerState.contextState.getState();
|
|
509
|
+
const setContext = routerState.contextState.setState;
|
|
510
|
+
const params = getParamsObject({
|
|
511
|
+
params: routeItem?.params,
|
|
512
|
+
pathname: location.pathname
|
|
513
|
+
});
|
|
514
|
+
if (!routeItem) throw new Error("Route not found");
|
|
515
|
+
if (!routeItem.actions) throw new Error("Route action creator not found");
|
|
516
|
+
const action = routeItem.actions({
|
|
517
|
+
context,
|
|
518
|
+
setContext,
|
|
519
|
+
params,
|
|
520
|
+
invalidate
|
|
521
|
+
})[actionKey];
|
|
522
|
+
if (!action) throw new Error(`Action "${actionKey}" not found`);
|
|
523
|
+
return {
|
|
524
|
+
currentAction: action,
|
|
525
|
+
invalidate
|
|
526
|
+
};
|
|
527
|
+
};
|
|
474
528
|
return {
|
|
475
529
|
state: {
|
|
476
530
|
isLoadingState: routerState.isLoadingState,
|
|
@@ -483,9 +537,9 @@ var createRouterInstance = () => {
|
|
|
483
537
|
prevPathnameRef: routerState.prevPathnameRef
|
|
484
538
|
},
|
|
485
539
|
runtime: {
|
|
486
|
-
navigate
|
|
487
|
-
invalidate
|
|
488
|
-
prefetch
|
|
540
|
+
navigate,
|
|
541
|
+
invalidate,
|
|
542
|
+
prefetch
|
|
489
543
|
},
|
|
490
544
|
hooks: {
|
|
491
545
|
useIsLoading: () => useGlobalState(routerState.isLoadingState),
|
|
@@ -494,7 +548,55 @@ var createRouterInstance = () => {
|
|
|
494
548
|
useRouteItemData: () => useGlobalState(routerState.routeItemDataState),
|
|
495
549
|
useCurrentLoaderState: () => useGlobalState(routerState.currentLoaderState),
|
|
496
550
|
useScrollMap: () => useGlobalState(routerState.scrollMapState),
|
|
497
|
-
useContextState: () => useGlobalState(routerState.contextState)
|
|
551
|
+
useContextState: () => useGlobalState(routerState.contextState),
|
|
552
|
+
useParams: () => {
|
|
553
|
+
const routeItemData = routerState.routeItemDataState.getState();
|
|
554
|
+
return getParamsObject({
|
|
555
|
+
params: routeItemData.routeItem?.params,
|
|
556
|
+
pathname: routeItemData.location.pathname
|
|
557
|
+
});
|
|
558
|
+
},
|
|
559
|
+
useNavigate: () => {
|
|
560
|
+
const { blockedRouteState } = routerState;
|
|
561
|
+
const { location } = routerState.routeItemDataState.getState();
|
|
562
|
+
return async (arg) => {
|
|
563
|
+
if (arg !== -1 && blockedRouteState.getState().from) {
|
|
564
|
+
const to = typeof arg === "object" ? arg.pathname : arg;
|
|
565
|
+
blockedRouteState.setState((prevState) => ({
|
|
566
|
+
...prevState,
|
|
567
|
+
to
|
|
568
|
+
}));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
if (arg === -1) return history.go(arg);
|
|
572
|
+
if (typeof arg === "string") {
|
|
573
|
+
if (arg !== location.pathname) await navigate({ pathname: arg });
|
|
574
|
+
} else if (JSON.stringify(arg) !== JSON.stringify(location)) await navigate(arg);
|
|
575
|
+
};
|
|
576
|
+
},
|
|
577
|
+
useRestoreScroll: () => {
|
|
578
|
+
const { pathname } = routerState.routeItemDataState.getState().location;
|
|
579
|
+
const scrollMap = routerState.scrollMapState.getState();
|
|
580
|
+
return () => {
|
|
581
|
+
if (scrollMap[pathname]) requestAnimationFrame(() => window.scrollTo({
|
|
582
|
+
top: scrollMap[pathname],
|
|
583
|
+
behavior: "smooth"
|
|
584
|
+
}));
|
|
585
|
+
};
|
|
586
|
+
},
|
|
587
|
+
useGetAction,
|
|
588
|
+
useAction: (action, options = {}) => {
|
|
589
|
+
const { currentAction, invalidate } = useGetAction(action);
|
|
590
|
+
return async (formData) => {
|
|
591
|
+
try {
|
|
592
|
+
const result = await currentAction(formData);
|
|
593
|
+
await invalidate();
|
|
594
|
+
options.onSuccess?.(result);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
options.onError?.(error);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
}
|
|
498
600
|
}
|
|
499
601
|
};
|
|
500
602
|
};
|
|
@@ -567,21 +669,16 @@ var useApplyCustomAnimation = (animationDuration) => {
|
|
|
567
669
|
}, [animationDuration]);
|
|
568
670
|
};
|
|
569
671
|
//#endregion
|
|
672
|
+
//#region hooks/useLocation.ts
|
|
673
|
+
var useLocation = () => {
|
|
674
|
+
const [routeItemData] = router.hooks.useRouteItemData();
|
|
675
|
+
return routeItemData.location;
|
|
676
|
+
};
|
|
677
|
+
//#endregion
|
|
570
678
|
//#region hooks/usePreserveScroll.ts
|
|
571
679
|
var usePreserveScroll = (preserveScroll) => {
|
|
572
|
-
const
|
|
573
|
-
const
|
|
574
|
-
const [scrollMap] = useScrollMap();
|
|
575
|
-
const { pathname } = routeItemData.location;
|
|
576
|
-
const restoreScroll = useCallback(() => {
|
|
577
|
-
if (!pathname || !scrollMap[pathname]) return;
|
|
578
|
-
requestAnimationFrame(() => {
|
|
579
|
-
window.scrollTo({
|
|
580
|
-
top: scrollMap[pathname],
|
|
581
|
-
behavior: "smooth"
|
|
582
|
-
});
|
|
583
|
-
});
|
|
584
|
-
}, [pathname, scrollMap]);
|
|
680
|
+
const restoreScroll = router.hooks.useRestoreScroll();
|
|
681
|
+
const { pathname } = useLocation();
|
|
585
682
|
useEffect(() => {
|
|
586
683
|
if (preserveScroll) restoreScroll();
|
|
587
684
|
}, [
|
|
@@ -615,7 +712,7 @@ var renderElement = (Component) => {
|
|
|
615
712
|
//#endregion
|
|
616
713
|
//#region components/Router.tsx
|
|
617
714
|
var EmptyBoundary = ({ children }) => children;
|
|
618
|
-
var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = false, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary, context: initialContext, defaultLoaderFallback, defaultErrorElement }) => {
|
|
715
|
+
var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = false, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary, context: initialContext, defaultLoaderFallback, defaultErrorElement, defaultRetry }) => {
|
|
619
716
|
const { useIsLoading, useLoaderFallback, useRouteItemData, useCurrentLoaderState } = router.hooks;
|
|
620
717
|
const [isLoading] = useIsLoading();
|
|
621
718
|
const [currentLoaderFallback] = useLoaderFallback();
|
|
@@ -629,7 +726,8 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
|
|
|
629
726
|
hoverPrefetchDelay,
|
|
630
727
|
showFallbackOnAnimation,
|
|
631
728
|
beforeLoad,
|
|
632
|
-
afterLoad
|
|
729
|
+
afterLoad,
|
|
730
|
+
defaultRetry
|
|
633
731
|
});
|
|
634
732
|
useApplyCustomAnimation(animationDuration);
|
|
635
733
|
useSetInitialContext(initialContext);
|
|
@@ -648,45 +746,8 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
|
|
|
648
746
|
});
|
|
649
747
|
};
|
|
650
748
|
//#endregion
|
|
651
|
-
//#region hooks/useLocation.ts
|
|
652
|
-
var useLocation = () => {
|
|
653
|
-
const [routeItemData] = router.hooks.useRouteItemData();
|
|
654
|
-
return routeItemData.location;
|
|
655
|
-
};
|
|
656
|
-
//#endregion
|
|
657
|
-
//#region hooks/useLatest.ts
|
|
658
|
-
var useLatest = (value) => {
|
|
659
|
-
const ref = useRef(value);
|
|
660
|
-
useEffect(() => {
|
|
661
|
-
ref.current = value;
|
|
662
|
-
}, [value]);
|
|
663
|
-
return ref;
|
|
664
|
-
};
|
|
665
|
-
//#endregion
|
|
666
749
|
//#region hooks/useNavigate.ts
|
|
667
|
-
var useNavigate =
|
|
668
|
-
const [blockedRoute, setBlockedRoute] = router.hooks.useBlockedRoute();
|
|
669
|
-
const locationRef = useLatest(useLocation());
|
|
670
|
-
const blockedRouteRef = useLatest(blockedRoute);
|
|
671
|
-
return useCallback(async (arg) => {
|
|
672
|
-
if (arg !== -1 && blockedRouteRef.current.from) {
|
|
673
|
-
const to = typeof arg === "object" ? arg.pathname : arg;
|
|
674
|
-
setBlockedRoute((prevState) => ({
|
|
675
|
-
...prevState,
|
|
676
|
-
to
|
|
677
|
-
}));
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
if (arg === -1) return history.go(arg);
|
|
681
|
-
if (typeof arg === "string") {
|
|
682
|
-
if (arg !== locationRef.current.pathname) await router.runtime.navigate({ pathname: arg });
|
|
683
|
-
} else if (JSON.stringify(arg) !== JSON.stringify(locationRef.current)) await router.runtime.navigate(arg);
|
|
684
|
-
}, [
|
|
685
|
-
blockedRouteRef,
|
|
686
|
-
locationRef,
|
|
687
|
-
setBlockedRoute
|
|
688
|
-
]);
|
|
689
|
-
};
|
|
750
|
+
var useNavigate = router.hooks.useNavigate;
|
|
690
751
|
//#endregion
|
|
691
752
|
//#region components/Link.tsx
|
|
692
753
|
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
@@ -749,46 +810,9 @@ var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_js
|
|
|
749
810
|
children
|
|
750
811
|
});
|
|
751
812
|
//#endregion
|
|
752
|
-
//#region hooks/useInvalidate.ts
|
|
753
|
-
var useInvalidate = () => router.runtime.invalidate;
|
|
754
|
-
//#endregion
|
|
755
|
-
//#region hooks/useParams.ts
|
|
756
|
-
var useParams = () => {
|
|
757
|
-
const [routeItemData] = router.hooks.useRouteItemData();
|
|
758
|
-
const { routeItem, location: { pathname } } = routeItemData;
|
|
759
|
-
return useMemo(() => routeItem ? getParamsObject({
|
|
760
|
-
params: routeItem?.params,
|
|
761
|
-
pathname
|
|
762
|
-
}) : void 0, [pathname, routeItem]);
|
|
763
|
-
};
|
|
764
|
-
//#endregion
|
|
765
|
-
//#region hooks/useGetAction.ts
|
|
766
|
-
var useGetAction = (actionKey) => {
|
|
767
|
-
const { useRouteItemData, useContextState } = router.hooks;
|
|
768
|
-
const invalidate = useInvalidate();
|
|
769
|
-
const [routeItemData] = useRouteItemData();
|
|
770
|
-
const [context, setContext] = useContextState();
|
|
771
|
-
const params = useParams();
|
|
772
|
-
const { routeItem } = routeItemData;
|
|
773
|
-
const latestContext = useLatest(context);
|
|
774
|
-
if (!routeItem) throw new Error("Route not found");
|
|
775
|
-
if (!routeItem.actions) throw new Error("Route action creator not found");
|
|
776
|
-
const action = routeItem.actions({
|
|
777
|
-
context: latestContext.current,
|
|
778
|
-
setContext,
|
|
779
|
-
params,
|
|
780
|
-
invalidate
|
|
781
|
-
})[actionKey];
|
|
782
|
-
if (!action) throw new Error(`Action "${actionKey}" not found`);
|
|
783
|
-
return {
|
|
784
|
-
currentAction: action,
|
|
785
|
-
invalidate
|
|
786
|
-
};
|
|
787
|
-
};
|
|
788
|
-
//#endregion
|
|
789
813
|
//#region components/Form.tsx
|
|
790
814
|
var Form = ({ children, action, onSuccess, onError, autoReset = true }) => {
|
|
791
|
-
const { currentAction, invalidate } = useGetAction(action);
|
|
815
|
+
const { currentAction, invalidate } = router.hooks.useGetAction(action);
|
|
792
816
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
793
817
|
const onSubmit = async (evt) => {
|
|
794
818
|
evt.preventDefault();
|
|
@@ -813,12 +837,18 @@ var Form = ({ children, action, onSuccess, onError, autoReset = true }) => {
|
|
|
813
837
|
});
|
|
814
838
|
};
|
|
815
839
|
//#endregion
|
|
840
|
+
//#region hooks/useParams.ts
|
|
841
|
+
var useParams = router.hooks.useParams;
|
|
842
|
+
//#endregion
|
|
816
843
|
//#region hooks/useLoaderState.ts
|
|
817
844
|
var useLoaderState = () => {
|
|
818
845
|
const [loaderState] = router.hooks.useCurrentLoaderState();
|
|
819
846
|
return loaderState;
|
|
820
847
|
};
|
|
821
848
|
//#endregion
|
|
849
|
+
//#region hooks/useInvalidate.ts
|
|
850
|
+
var useInvalidate = () => router.runtime.invalidate;
|
|
851
|
+
//#endregion
|
|
822
852
|
//#region hooks/useBlocker.ts
|
|
823
853
|
var useBlocker = (blockerFn) => {
|
|
824
854
|
const { hooks: { useBlockedRoute, useRouteItemData }, runtime: { navigate } } = router;
|
|
@@ -864,25 +894,7 @@ var useBlocker = (blockerFn) => {
|
|
|
864
894
|
};
|
|
865
895
|
//#endregion
|
|
866
896
|
//#region hooks/useAction.ts
|
|
867
|
-
var useAction =
|
|
868
|
-
const { currentAction, invalidate } = useGetAction(action);
|
|
869
|
-
const latestOnSuccess = useLatest(options?.onSuccess);
|
|
870
|
-
const latestOnError = useLatest(options?.onError);
|
|
871
|
-
return useCallback(async (formData) => {
|
|
872
|
-
try {
|
|
873
|
-
const result = await currentAction(formData);
|
|
874
|
-
await invalidate();
|
|
875
|
-
latestOnSuccess.current?.(result);
|
|
876
|
-
} catch (error) {
|
|
877
|
-
latestOnError.current?.(error);
|
|
878
|
-
}
|
|
879
|
-
}, [
|
|
880
|
-
currentAction,
|
|
881
|
-
invalidate,
|
|
882
|
-
latestOnError,
|
|
883
|
-
latestOnSuccess
|
|
884
|
-
]);
|
|
885
|
-
};
|
|
897
|
+
var useAction = router.hooks.useAction;
|
|
886
898
|
//#endregion
|
|
887
899
|
//#region hooks/useRouterContext.ts
|
|
888
900
|
var useRouterContext = () => {
|
|
@@ -922,6 +934,15 @@ var useSearch = () => {
|
|
|
922
934
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
923
935
|
};
|
|
924
936
|
//#endregion
|
|
937
|
+
//#region hooks/useLatest.ts
|
|
938
|
+
var useLatest = (value) => {
|
|
939
|
+
const ref = useRef(value);
|
|
940
|
+
useEffect(() => {
|
|
941
|
+
ref.current = value;
|
|
942
|
+
}, [value]);
|
|
943
|
+
return ref;
|
|
944
|
+
};
|
|
945
|
+
//#endregion
|
|
925
946
|
//#region hooks/useSearchParams.ts
|
|
926
947
|
var useSearchParams = () => {
|
|
927
948
|
const search = useSearch();
|
|
@@ -1,5 +1,2 @@
|
|
|
1
1
|
import { type InvalidateOptions, RevalidateCache, RouterState } from '../types';
|
|
2
|
-
export declare const createInvalidate: ({ routeItemDataState, loaderStateRef, timestampMap, currentLoaderState, contextState }: RouterState, revalidateCache: RevalidateCache) =>
|
|
3
|
-
(pathList?: string[]): Promise<void>;
|
|
4
|
-
(path?: string, options?: InvalidateOptions): Promise<void>;
|
|
5
|
-
};
|
|
2
|
+
export declare const createInvalidate: ({ routeItemDataState, loaderStateRef, timestampMap, currentLoaderState, contextState }: RouterState, revalidateCache: RevalidateCache) => (pathList?: string | string[], options?: InvalidateOptions) => Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Location, RevalidateCache, RouterState } from '../types';
|
|
2
|
-
export declare const createNavigate: (
|
|
2
|
+
export declare const createNavigate: (routerState: RouterState, revalidateCache: RevalidateCache) => (nextLocation: Location) => Promise<void>;
|
package/dist/types.d.ts
CHANGED
|
@@ -24,6 +24,7 @@ export type ClientRouteItem = {
|
|
|
24
24
|
fallback?: RenderElement;
|
|
25
25
|
children?: ClientRouteItem[];
|
|
26
26
|
staleTime?: number;
|
|
27
|
+
retry?: Retry;
|
|
27
28
|
beforeLoad?: BeforeLoad;
|
|
28
29
|
afterLoad?: (arg: {
|
|
29
30
|
context: Record<string, unknown>;
|
|
@@ -68,12 +69,18 @@ export type RouteItemData = {
|
|
|
68
69
|
location: Location;
|
|
69
70
|
routeItem: RouteItem | undefined;
|
|
70
71
|
};
|
|
72
|
+
type ObjectRetry = {
|
|
73
|
+
count: number;
|
|
74
|
+
delay: number;
|
|
75
|
+
};
|
|
76
|
+
export type Retry = number | ObjectRetry | undefined;
|
|
71
77
|
export type RouterProps = {
|
|
72
78
|
routes: RouteItem[];
|
|
73
79
|
isAnimated?: boolean;
|
|
74
80
|
animationDuration?: number;
|
|
75
81
|
spinner?: boolean;
|
|
76
82
|
preserveScroll?: boolean;
|
|
83
|
+
defaultRetry?: Retry;
|
|
77
84
|
defaultLoaderFallback?: RenderElement;
|
|
78
85
|
defaultErrorElement?: RenderElement;
|
|
79
86
|
showFallbackOnAnimation?: boolean;
|
|
@@ -116,12 +123,25 @@ export type RouterType = {
|
|
|
116
123
|
}>>;
|
|
117
124
|
useLoaderFallback: () => ReturnType<typeof useGlobalState<RenderElement | undefined>>;
|
|
118
125
|
useRouteItemData: () => ReturnType<typeof useGlobalState<RouteItemData>>;
|
|
119
|
-
useCurrentLoaderState: () => ReturnType<typeof useGlobalState<LoaderState
|
|
126
|
+
useCurrentLoaderState: () => ReturnType<typeof useGlobalState<LoaderState>>;
|
|
120
127
|
useScrollMap: () => ReturnType<typeof useGlobalState<Record<string, number>>>;
|
|
121
128
|
useContextState: () => ReturnType<typeof useGlobalState<Record<string, unknown>>>;
|
|
129
|
+
useParams: <T>() => T;
|
|
130
|
+
useNavigate: () => (arg: Location | string | -1) => Promise<void>;
|
|
131
|
+
useGetAction: (actionKey: string) => {
|
|
132
|
+
currentAction: (arg: FormData) => Promise<unknown> | Promise<void> | void | unknown;
|
|
133
|
+
invalidate: (pathList?: string | string[], options?: InvalidateOptions) => Promise<void>;
|
|
134
|
+
};
|
|
135
|
+
useRestoreScroll: () => () => void;
|
|
136
|
+
useAction: (action: string, options?: Options) => (arg: FormData) => Promise<void>;
|
|
122
137
|
};
|
|
123
138
|
};
|
|
124
139
|
export type InvalidateOptions = {
|
|
125
140
|
withChildren?: boolean;
|
|
126
141
|
};
|
|
127
142
|
export type RevalidateCache = ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown> | undefined;
|
|
143
|
+
export type Options = Partial<{
|
|
144
|
+
onSuccess: (args: unknown) => void;
|
|
145
|
+
onError: (args: unknown) => void;
|
|
146
|
+
}> | undefined;
|
|
147
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RevalidateCacheArgs, RouterState } from '../types';
|
|
2
|
-
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname }: RevalidateCacheArgs) => any
|
|
2
|
+
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname }: RevalidateCacheArgs, retried?: number) => Promise<any>;
|
package/package.json
CHANGED