clear-react-router 1.8.8 → 1.9.0
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
CHANGED
|
@@ -48,10 +48,11 @@ It provides first-class support for:
|
|
|
48
48
|
| `defaultLoaderFallback` | `ReactElement \| () => ReactElement` | `optional` | Default loading fallback for every route loader |
|
|
49
49
|
| `defaultErrorElement` | `ReactElement \| () => ReactElement` | `optional` | Default error fallback for every route |
|
|
50
50
|
| `defaultRetry` | `number \| { count: number; delay: number }` | `optional` | Default cache revalidation retry policy for all routes |
|
|
51
|
+
| `defaultStaleTime` | `number` | `optional` | Default time in milliseconds before cached loader data is considered stale |
|
|
51
52
|
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | `undefined` | Runs before every navigation. Useful for authentication, analytics, or updating shared context. |
|
|
52
53
|
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | `undefined` | Runs after every successful navigation. Useful for analytics, page tracking, or other global side effects. |
|
|
53
54
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
54
|
-
| `
|
|
55
|
+
| `defaultPreserveScroll` | `boolean \| undefined` | `true` | Default value for save and restore scroll position when navigating between pages |
|
|
55
56
|
| `showFallbackOnAnimation` | `boolean \| undefined` | `false` | Show `loaderFallback` even when `isAnimated` is `true` (instead of spinner) |
|
|
56
57
|
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `'hover'` | Default prefetch strategy for all `<Link>` components |
|
|
57
58
|
| `hoverPrefetchDelay` | `number` | `150` | Delay in milliseconds before prefetching on hover (only for `'hover'` strategy) |
|
|
@@ -76,15 +77,27 @@ Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
|
76
77
|
|----------|------|-------------|
|
|
77
78
|
| `path` | `string` | Route path, e.g., `/user/:userId` |
|
|
78
79
|
| `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` |
|
|
80
|
+
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | Runs before every route navigation. Auth checks and redirects. Can update context via `setContext`. `redirect` is provided by the router |
|
|
81
|
+
| `loader` | `({ params, context, setContext, searchParams }) => Promise<unknown>` | Fetch data using route params, search params, and context. Can update context via `setContext` |
|
|
82
|
+
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | Runs after a successful navigation once the route has finished loading. Analytics, side effects after data is loaded. Can update context via `setContext` |
|
|
82
83
|
| `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
|
|
83
84
|
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback for the route's `loader`. Overrides the global `defaultLoaderFallback` set in `Router` |
|
|
84
|
-
| `retry` | `number \| { count: number; delay: number }` | `
|
|
85
|
+
| `retry` | `number \| { count: number; delay: number }` | `undefined` | Overrides the global cache revalidation retry policy for this route |
|
|
85
86
|
| `errorElement` | `ReactElement \| () => ReactElement` | Error fallback for the route. Overrides the global `defaultErrorElement` set in `Router` |
|
|
86
|
-
| `staleTime` | `number` | Time in
|
|
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
|
|
87
|
+
| `staleTime` | `number` | Time in milliseconds before cached loader data is considered stale. Overrides Router.defaultStaleTime. If neither value is provided, cached data never expires |
|
|
88
|
+
| `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 invalidate cached loader data using the router-provided `invalidate` |
|
|
89
|
+
| `pollingInterval` | `number \| undefined` | `undefined` | Polling interval (in milliseconds) for automatically revalidating data while the route is active |
|
|
90
|
+
| `preserveScroll` | `boolean \| undefined` | `undefined` | Save and restore route scroll position when navigating between pages |
|
|
91
|
+
|
|
92
|
+
Loader arguments:
|
|
93
|
+
```ts
|
|
94
|
+
{
|
|
95
|
+
params: Record<string, string>; // Route parameters
|
|
96
|
+
context: Record<string, unknown>; // Router context
|
|
97
|
+
setContext: Dispatch<SetStateAction<Record<string, unknown>>>; // Updates the router context
|
|
98
|
+
searchParams: Record<string, string>; // URL search parameters
|
|
99
|
+
}
|
|
100
|
+
```
|
|
88
101
|
|
|
89
102
|
### `Link`
|
|
90
103
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types';
|
|
2
|
-
export declare const Router: ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated, spinner,
|
|
2
|
+
export declare const Router: ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated, spinner, defaultPreserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, context: initialContext, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -8,6 +8,8 @@ declare class RouterConfig {
|
|
|
8
8
|
beforeLoad?: ClientRouteItem['beforeLoad'];
|
|
9
9
|
afterLoad?: ClientRouteItem['afterLoad'];
|
|
10
10
|
defaultRetry?: RouterProps['defaultRetry'];
|
|
11
|
+
defaultStaleTime?: RouterProps['defaultStaleTime'];
|
|
12
|
+
defaultPreserveScroll?: RouterProps['defaultPreserveScroll'];
|
|
11
13
|
configure(config: Partial<RouterConfig>): void;
|
|
12
14
|
}
|
|
13
15
|
export declare const routerConfig: RouterConfig;
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { RouteItemData } from '../types';
|
|
2
|
+
export declare const usePreserveScroll: ({ routeItem, location: { pathname } }: RouteItemData) => void;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Suspense, createContext, lazy, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
1
|
+
import { Suspense, createContext, lazy, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
2
|
//#region \0rolldown/runtime.js
|
|
3
3
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
4
4
|
//#endregion
|
|
@@ -96,6 +96,8 @@ var RouterConfig = class {
|
|
|
96
96
|
_defineProperty(this, "beforeLoad", void 0);
|
|
97
97
|
_defineProperty(this, "afterLoad", void 0);
|
|
98
98
|
_defineProperty(this, "defaultRetry", void 0);
|
|
99
|
+
_defineProperty(this, "defaultStaleTime", void 0);
|
|
100
|
+
_defineProperty(this, "defaultPreserveScroll", void 0);
|
|
99
101
|
}
|
|
100
102
|
configure(config) {
|
|
101
103
|
Object.assign(this, config);
|
|
@@ -120,10 +122,11 @@ var createCommitNavigation = (navigationExecutor, prevPathnameRef) => (nextLocat
|
|
|
120
122
|
//#region utils/isCacheItemFresh.ts
|
|
121
123
|
var createIsCacheItemFresh = (timestampMap) => ({ routeItem, pathname }) => {
|
|
122
124
|
if (!routeItem) return true;
|
|
123
|
-
const
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
const timestamp = timestampMap.get(pathname);
|
|
126
|
+
if (timestamp === void 0) return false;
|
|
127
|
+
const staleTime = routeItem.staleTime ?? routerConfig.defaultStaleTime;
|
|
128
|
+
if (staleTime === void 0) return true;
|
|
129
|
+
return Date.now() - timestamp <= staleTime;
|
|
127
130
|
};
|
|
128
131
|
//#endregion
|
|
129
132
|
//#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
|
|
@@ -218,8 +221,9 @@ var findRoute = (pathname, includeAll) => {
|
|
|
218
221
|
//#endregion
|
|
219
222
|
//#region runtime/navigate.ts
|
|
220
223
|
var navigationSeq = 0;
|
|
224
|
+
var interval = 0;
|
|
221
225
|
var createNavigate = (routerState, revalidateCache) => {
|
|
222
|
-
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap, pendingPathRef } = routerState;
|
|
226
|
+
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap, pendingPathRef, routeItemDataState } = routerState;
|
|
223
227
|
const commitNavigation = createCommitNavigation(createCommitState(routerState), prevPathnameRef);
|
|
224
228
|
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
225
229
|
const getContext = () => ({
|
|
@@ -273,15 +277,28 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
273
277
|
pathname: location.pathname
|
|
274
278
|
}) || isAnimated && !showFallback ? void 0 : routeItem?.loaderFallback);
|
|
275
279
|
};
|
|
280
|
+
const beforeEachLoad = (location) => {
|
|
281
|
+
window.clearInterval(interval);
|
|
282
|
+
if (routeItemDataState.getState().location.pathname !== location.pathname) isLoadingState.setState(true);
|
|
283
|
+
pendingPathRef.set(location.pathname);
|
|
284
|
+
};
|
|
285
|
+
const afterEachLoad = (routeItem) => {
|
|
286
|
+
pendingPathRef.set("");
|
|
287
|
+
if (!routeItem?.pollingInterval) return;
|
|
288
|
+
interval = window.setInterval(() => revalidateCache({
|
|
289
|
+
routeItem,
|
|
290
|
+
pathname: location.pathname
|
|
291
|
+
}), routeItem.pollingInterval);
|
|
292
|
+
};
|
|
276
293
|
const loader = async (routeItem, location) => {
|
|
277
294
|
if (!routeItem?.loader) return;
|
|
278
|
-
|
|
279
|
-
pendingPathRef.set(location.pathname);
|
|
295
|
+
beforeEachLoad(location);
|
|
280
296
|
await revalidateCache({
|
|
281
297
|
routeItem,
|
|
282
|
-
pathname: location.pathname
|
|
298
|
+
pathname: location.pathname,
|
|
299
|
+
search: location.search
|
|
283
300
|
});
|
|
284
|
-
|
|
301
|
+
afterEachLoad(routeItem);
|
|
285
302
|
};
|
|
286
303
|
const afterLoad = async (routeItem, params) => {
|
|
287
304
|
const { afterLoad } = routerConfig;
|
|
@@ -399,7 +416,7 @@ var getRetry = (routeItem) => {
|
|
|
399
416
|
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
400
417
|
var createRevalidateCache = (routerState) => {
|
|
401
418
|
const { loaderStateRef, timestampMap, contextState } = routerState;
|
|
402
|
-
const revalidateCache = async ({ routeItem, pathname }, retried = 0) => {
|
|
419
|
+
const revalidateCache = async ({ routeItem, pathname, search = "" }, retried = 0) => {
|
|
403
420
|
if (!routeItem?.loader) return;
|
|
404
421
|
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
405
422
|
if (loadingPromises.has(pathname)) return loadingPromises.get(pathname);
|
|
@@ -416,18 +433,20 @@ var createRevalidateCache = (routerState) => {
|
|
|
416
433
|
const context = contextState.getState();
|
|
417
434
|
const setContext = contextState.setState;
|
|
418
435
|
const params = getParamsObject(routeItem, pathname);
|
|
436
|
+
const searchParams = Object.fromEntries(new URLSearchParams(search).entries());
|
|
419
437
|
const result = await routeItem?.loader({
|
|
420
438
|
params,
|
|
421
439
|
context,
|
|
422
|
-
setContext
|
|
440
|
+
setContext,
|
|
441
|
+
searchParams
|
|
423
442
|
});
|
|
424
|
-
timestampMap.set(pathname
|
|
443
|
+
timestampMap.set(`${pathname}${search}`, Date.now());
|
|
425
444
|
loaderStateRef.set((prev) => ({
|
|
426
445
|
...prev,
|
|
427
446
|
data: result,
|
|
428
447
|
loaderError: null
|
|
429
448
|
}));
|
|
430
|
-
loaderMapRef[pathname] = loaderStateRef.value;
|
|
449
|
+
loaderMapRef[`${pathname}${search}`] = loaderStateRef.value;
|
|
431
450
|
return {
|
|
432
451
|
data: result,
|
|
433
452
|
error: null
|
|
@@ -435,7 +454,7 @@ var createRevalidateCache = (routerState) => {
|
|
|
435
454
|
} catch (error) {
|
|
436
455
|
const retry = getRetry(routeItem);
|
|
437
456
|
if (retry && retry.count > retried) {
|
|
438
|
-
loadingPromises.delete(pathname);
|
|
457
|
+
loadingPromises.delete(`${pathname}${search}`);
|
|
439
458
|
if (retry.delay) await sleep(retry.delay);
|
|
440
459
|
await revalidateCache({
|
|
441
460
|
routeItem,
|
|
@@ -457,10 +476,10 @@ var createRevalidateCache = (routerState) => {
|
|
|
457
476
|
};
|
|
458
477
|
}
|
|
459
478
|
} finally {
|
|
460
|
-
loadingPromises.delete(pathname);
|
|
479
|
+
loadingPromises.delete(`${pathname}${search}`);
|
|
461
480
|
}
|
|
462
481
|
})();
|
|
463
|
-
loadingPromises.set(pathname
|
|
482
|
+
loadingPromises.set(`${pathname}${search}`, promise);
|
|
464
483
|
return promise;
|
|
465
484
|
};
|
|
466
485
|
return revalidateCache;
|
|
@@ -504,8 +523,8 @@ var createRouterInstance = () => {
|
|
|
504
523
|
timestampMap: /* @__PURE__ */ new Map()
|
|
505
524
|
};
|
|
506
525
|
const revalidateCache = createRevalidateCache(routerState);
|
|
507
|
-
const navigate = createNavigate(routerState, revalidateCache);
|
|
508
526
|
const invalidate = createInvalidate(routerState, revalidateCache);
|
|
527
|
+
const navigate = createNavigate(routerState, revalidateCache);
|
|
509
528
|
const prefetch = createPrefetch(revalidateCache);
|
|
510
529
|
const useGetAction = (actionKey) => {
|
|
511
530
|
const { routeItem } = routerState.routeItemDataState.getState();
|
|
@@ -665,16 +684,10 @@ var useApplyCustomAnimation = (animationDuration) => {
|
|
|
665
684
|
}, [animationDuration]);
|
|
666
685
|
};
|
|
667
686
|
//#endregion
|
|
668
|
-
//#region hooks/useLocation.ts
|
|
669
|
-
var useLocation = () => {
|
|
670
|
-
const [routeItemData] = router.hooks.useRouteItemData();
|
|
671
|
-
return routeItemData.location;
|
|
672
|
-
};
|
|
673
|
-
//#endregion
|
|
674
687
|
//#region hooks/usePreserveScroll.ts
|
|
675
|
-
var usePreserveScroll = (
|
|
688
|
+
var usePreserveScroll = ({ routeItem, location: { pathname } }) => {
|
|
689
|
+
const preserveScroll = routeItem?.preserveScroll === void 0 ? routerConfig.defaultPreserveScroll : routeItem.preserveScroll;
|
|
676
690
|
const restoreScroll = router.hooks.useRestoreScroll();
|
|
677
|
-
const { pathname } = useLocation();
|
|
678
691
|
useEffect(() => {
|
|
679
692
|
if (preserveScroll) restoreScroll();
|
|
680
693
|
}, [
|
|
@@ -686,7 +699,7 @@ var usePreserveScroll = (preserveScroll) => {
|
|
|
686
699
|
//#endregion
|
|
687
700
|
//#region hooks/useSetRouterConfig.ts
|
|
688
701
|
var useSetRouterConfig = (routerProps) => {
|
|
689
|
-
|
|
702
|
+
useLayoutEffect(() => routerConfig.configure(routerProps), [routerProps]);
|
|
690
703
|
};
|
|
691
704
|
//#endregion
|
|
692
705
|
//#region hooks/useSetInitialContext.ts
|
|
@@ -708,7 +721,7 @@ var renderElement = (Component) => {
|
|
|
708
721
|
//#endregion
|
|
709
722
|
//#region components/Router.tsx
|
|
710
723
|
var EmptyBoundary = ({ children }) => children;
|
|
711
|
-
var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = false, spinner = true,
|
|
724
|
+
var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = false, spinner = true, defaultPreserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary, context: initialContext, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime }) => {
|
|
712
725
|
const { useIsLoading, useLoaderFallback, useRouteItemData, useCurrentLoaderState } = router.hooks;
|
|
713
726
|
const [isLoading] = useIsLoading();
|
|
714
727
|
const [currentLoaderFallback] = useLoaderFallback();
|
|
@@ -723,15 +736,17 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
|
|
|
723
736
|
showFallbackOnAnimation,
|
|
724
737
|
beforeLoad,
|
|
725
738
|
afterLoad,
|
|
726
|
-
defaultRetry
|
|
739
|
+
defaultRetry,
|
|
740
|
+
defaultStaleTime,
|
|
741
|
+
defaultPreserveScroll
|
|
727
742
|
});
|
|
728
743
|
useApplyCustomAnimation(animationDuration);
|
|
729
744
|
useSetInitialContext(initialContext);
|
|
730
|
-
usePreserveScroll(
|
|
745
|
+
usePreserveScroll(routeItemData);
|
|
746
|
+
const { routeItem, location } = routeItemData;
|
|
731
747
|
const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
|
|
732
748
|
const showSpinner = spinner && isAnimated && isLoading;
|
|
733
749
|
const loadingContent = !showErrorElement && isLoading;
|
|
734
|
-
const { routeItem, location } = routeItemData;
|
|
735
750
|
if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(currentLoaderFallback || defaultLoaderFallback);
|
|
736
751
|
if (!showFallbackOnAnimation && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
|
|
737
752
|
if (!routeItem) return null;
|
|
@@ -752,6 +767,12 @@ var useIsRoutePending = (routePath) => {
|
|
|
752
767
|
//#region hooks/useNavigate.ts
|
|
753
768
|
var useNavigate = router.hooks.useNavigate;
|
|
754
769
|
//#endregion
|
|
770
|
+
//#region hooks/useLocation.ts
|
|
771
|
+
var useLocation = () => {
|
|
772
|
+
const [routeItemData] = router.hooks.useRouteItemData();
|
|
773
|
+
return routeItemData.location;
|
|
774
|
+
};
|
|
775
|
+
//#endregion
|
|
755
776
|
//#region components/Link.tsx
|
|
756
777
|
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, onClick, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
|
|
757
778
|
const isPending = useIsRoutePending(to);
|
package/dist/types.d.ts
CHANGED
|
@@ -18,13 +18,16 @@ export type ClientRouteItem = {
|
|
|
18
18
|
params: Record<string, string>;
|
|
19
19
|
context: Record<string, unknown>;
|
|
20
20
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
21
|
+
searchParams: Record<string, string>;
|
|
21
22
|
}): Promise<unknown>;
|
|
22
23
|
loaderFallback?: RenderElement;
|
|
23
24
|
errorElement?: RenderElement;
|
|
24
25
|
fallback?: RenderElement;
|
|
25
26
|
children?: ClientRouteItem[];
|
|
26
27
|
staleTime?: number;
|
|
28
|
+
pollingInterval?: number;
|
|
27
29
|
retry?: Retry;
|
|
30
|
+
preserveScroll?: boolean;
|
|
28
31
|
beforeLoad?: BeforeLoad;
|
|
29
32
|
afterLoad?: (arg: {
|
|
30
33
|
context: Record<string, unknown>;
|
|
@@ -55,6 +58,7 @@ export type BlockerState = 'blocked' | 'unblocked' | 'charged';
|
|
|
55
58
|
export type RevalidateCacheArgs = {
|
|
56
59
|
pathname: string;
|
|
57
60
|
routeItem?: RouteItem;
|
|
61
|
+
search?: string;
|
|
58
62
|
};
|
|
59
63
|
export type LoaderState<T = unknown> = {
|
|
60
64
|
data: T;
|
|
@@ -79,8 +83,9 @@ export type RouterProps = {
|
|
|
79
83
|
isAnimated?: boolean;
|
|
80
84
|
animationDuration?: number;
|
|
81
85
|
spinner?: boolean;
|
|
82
|
-
|
|
86
|
+
defaultPreserveScroll?: boolean;
|
|
83
87
|
defaultRetry?: Retry;
|
|
88
|
+
defaultStaleTime?: number;
|
|
84
89
|
defaultLoaderFallback?: RenderElement;
|
|
85
90
|
defaultErrorElement?: RenderElement;
|
|
86
91
|
showFallbackOnAnimation?: boolean;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RevalidateCacheArgs, RouterState } from '../types';
|
|
2
|
-
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname }: RevalidateCacheArgs, retried?: number) => Promise<any>;
|
|
2
|
+
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search }: RevalidateCacheArgs, retried?: number) => Promise<any>;
|