clear-react-router 2.0.0 → 2.0.3
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 +16 -6
- package/dist/components/Router.d.ts +1 -1
- package/dist/config/routerConfig.d.ts +1 -0
- package/dist/index.js +75 -33
- package/dist/types.d.ts +12 -4
- package/dist/utils/isCacheItemFresh.d.ts +1 -1
- package/dist/utils/revalidateCache.d.ts +7 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,17 +22,17 @@ It provides first-class support for:
|
|
|
22
22
|
## Features
|
|
23
23
|
|
|
24
24
|
- **Nested Routes** - Organize your UI with nested layouts and routes
|
|
25
|
-
- **Data Loading** - Built-in loaders with caching
|
|
25
|
+
- **Data Loading** - Built-in loaders with TTL-based caching (`staleTime`)
|
|
26
26
|
- **Navigation Blocking** - Prevent accidental navigation with `useBlocker`
|
|
27
27
|
- **Smooth Animations** - Page transitions with fade effect (customizable duration)
|
|
28
28
|
- **Static Layout** — Keep navbar, footer, and other elements outside the router to avoid unnecessary re-renders
|
|
29
29
|
- **Programmatic Redirects** - Redirect from beforeLoad hook
|
|
30
30
|
- **Cache invalidation** - Manual route invalidation
|
|
31
|
+
- **Bounded Cache** - Automatically evicts least recently used entries once `maxCacheSize` is reached, keeping memory usage predictable in long sessions
|
|
31
32
|
- **Prefetching** - Preload data on hover for instant navigation
|
|
32
33
|
- **Lazy Loading** - Code-split your routes with dynamic imports for optimal performance
|
|
33
34
|
- **Scroll Restoration** — Automatically saves and restores scroll position when navigating back to a page (preserves user's scroll position)
|
|
34
35
|
- **Optimistic navigation** — Instantly renders stale cached data while fresh data is loaded in the background.
|
|
35
|
-
- **Flexible API** - Use components or hooks as you prefer
|
|
36
36
|
- **Browser History** - Full support for browser back/forward buttons
|
|
37
37
|
- **Context-aware** - Pass and update context through routes
|
|
38
38
|
|
|
@@ -43,6 +43,7 @@ It provides first-class support for:
|
|
|
43
43
|
| Prop | Type | Default | Description |
|
|
44
44
|
|------|------|---------|-------------|
|
|
45
45
|
| `routes` | `RouteItem[]` | required | Array of route configurations |
|
|
46
|
+
| `maxCacheSize` | `number \| undefined` | 60 for mobile, 150 for desktop | Maximum number of cached loader entries. Once the limit is reached, the least recently used entries are evicted |
|
|
46
47
|
| `isAnimated` | `boolean \| undefined` | `false` | Enable smooth page fade transitions |
|
|
47
48
|
| `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
|
|
48
49
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
@@ -79,7 +80,7 @@ Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
|
79
80
|
| `path` | `string` | Route path, e.g., `/user/:userId` |
|
|
80
81
|
| `element` | `ReactElement \| () => ReactElement \| LazyComponent` | Component to render |
|
|
81
82
|
| `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 |
|
|
82
|
-
| `loader` | `({ params, context, setContext, searchParams }) => Promise<unknown>` | Fetch data using route params, search params, and context. Can update context via `setContext` |
|
|
83
|
+
| `loader` | `({ params, context, setContext, searchParams, signal }) => Promise<unknown>` | Fetch data using route params, search params, abort controller signal and context. Can update context via `setContext` |
|
|
83
84
|
| `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` |
|
|
84
85
|
| `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
|
|
85
86
|
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback for the route's `loader`. Overrides the global `defaultLoaderFallback` set in `Router` |
|
|
@@ -98,6 +99,7 @@ Loader arguments:
|
|
|
98
99
|
context: Record<string, unknown>; // Router context
|
|
99
100
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>; // Updates the router context
|
|
100
101
|
searchParams: Record<string, string>; // URL search parameters
|
|
102
|
+
signal: AbortSignal; // AbortController signal
|
|
101
103
|
}
|
|
102
104
|
```
|
|
103
105
|
|
|
@@ -563,16 +565,24 @@ const UserProfile = () => {
|
|
|
563
565
|
```
|
|
564
566
|
|
|
565
567
|
### Caching behavior:
|
|
568
|
+
|
|
566
569
|
- The loader result is cached and reused when navigating back to the same route (e.g., from /user/123 back to /user/456 it will be a new request because different params, but from /user/456 to /user/456 — cache hit).
|
|
567
|
-
- Use staleTime in route config to control how long cache is considered fresh:
|
|
568
|
-
|
|
570
|
+
- Use `staleTime` in route config to control how long cache is considered fresh:
|
|
571
|
+
|
|
572
|
+
```tsx
|
|
569
573
|
{
|
|
570
574
|
path: '/user/:userId',
|
|
571
575
|
loader: async ({ params }) => fetchUser(params.userId),
|
|
572
576
|
staleTime: 60000, // 1 minute — cache is fresh for 60 seconds
|
|
573
577
|
}
|
|
574
578
|
```
|
|
575
|
-
|
|
579
|
+
|
|
580
|
+
- Stale entries are cleaned up on every navigation, so cache growth stays tied to how often you actually revisit stale data — not to how long the session lasts.
|
|
581
|
+
- On top of that, the cache is bounded by `maxCacheSize` — once the limit is reached, the least recently used entry is evicted to make room for a new one, regardless of whether it's still fresh. This caps memory usage for apps with many high-cardinality dynamic routes (e.g. `/product/:id` across a large catalog). It defaults to a device-aware value (lower on mobile) and can be overridden on the `Router`:
|
|
582
|
+
|
|
583
|
+
```tsx
|
|
584
|
+
<Router routes={routes} maxCacheSize={200} />
|
|
585
|
+
```
|
|
576
586
|
|
|
577
587
|
|
|
578
588
|
### `useInvalidate()`
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types';
|
|
2
|
-
export declare const Router: ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated, spinner, defaultPreserveScroll, showFallbackOnAnimation, defaultPrefetch, defaultHoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
2
|
+
export declare const Router: ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated, spinner, defaultPreserveScroll, showFallbackOnAnimation, maxCacheSize, defaultPrefetch, defaultHoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ClientRouteItem, RouterProps } from '../types';
|
|
2
2
|
declare class RouterConfig {
|
|
3
3
|
routes: RouterProps['routes'];
|
|
4
|
+
maxCacheSize: number;
|
|
4
5
|
defaultPrefetch: RouterProps['defaultPrefetch'];
|
|
5
6
|
isAnimated: RouterProps['isAnimated'];
|
|
6
7
|
defaultHoverPrefetchDelay: number;
|
package/dist/index.js
CHANGED
|
@@ -86,6 +86,7 @@ function _defineProperty(e, r, t) {
|
|
|
86
86
|
var RouterConfig = class {
|
|
87
87
|
constructor() {
|
|
88
88
|
_defineProperty(this, "routes", []);
|
|
89
|
+
_defineProperty(this, "maxCacheSize", 0);
|
|
89
90
|
_defineProperty(this, "defaultPrefetch", "hover");
|
|
90
91
|
_defineProperty(this, "isAnimated", false);
|
|
91
92
|
_defineProperty(this, "defaultHoverPrefetchDelay", 150);
|
|
@@ -113,8 +114,8 @@ var createCommitNavigation = (navigationExecutor, routeItemDataState) => (nextLo
|
|
|
113
114
|
};
|
|
114
115
|
//#endregion
|
|
115
116
|
//#region utils/isCacheItemFresh.ts
|
|
116
|
-
var createIsCacheItemFresh = (loaderMap) => (
|
|
117
|
-
const item = loaderMap.get(
|
|
117
|
+
var createIsCacheItemFresh = (loaderMap) => (path) => {
|
|
118
|
+
const item = loaderMap.get(path);
|
|
118
119
|
if (item === void 0) return false;
|
|
119
120
|
const resolvedStaleTime = item.staleTime ?? routerConfig.defaultStaleTime;
|
|
120
121
|
if (resolvedStaleTime === void 0) return true;
|
|
@@ -220,12 +221,18 @@ var findRoute = (pathname, includeAll) => {
|
|
|
220
221
|
};
|
|
221
222
|
//#endregion
|
|
222
223
|
//#region runtime/navigate.ts
|
|
223
|
-
var navigationSeq = 0;
|
|
224
|
-
var interval = 0;
|
|
225
224
|
var createNavigate = (routerState, revalidateCache) => {
|
|
225
|
+
let navigationSeq = 0;
|
|
226
|
+
let interval = 0;
|
|
227
|
+
let abortController = null;
|
|
226
228
|
const { loaderStateRef, scrollMapState, pendingState, contextState, loaderMap, routeItemDataState } = routerState;
|
|
227
229
|
const commitNavigation = createCommitNavigation(createCommitState(routerState), routeItemDataState);
|
|
228
230
|
const isCacheItemFresh = createIsCacheItemFresh(loaderMap);
|
|
231
|
+
const createSignal = () => {
|
|
232
|
+
abortController?.abort();
|
|
233
|
+
abortController = new AbortController();
|
|
234
|
+
return abortController.signal;
|
|
235
|
+
};
|
|
229
236
|
const getContext = () => ({
|
|
230
237
|
context: contextState.getState(),
|
|
231
238
|
setContext: contextState.setState
|
|
@@ -272,37 +279,44 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
272
279
|
[prevPathname]: scrollPosition
|
|
273
280
|
};
|
|
274
281
|
});
|
|
275
|
-
|
|
282
|
+
const path = `${location.pathname}${location.search}`;
|
|
283
|
+
if (routeItem?.optimistic && loaderMap.has(path)) {
|
|
276
284
|
routeItemDataState.setState({
|
|
277
285
|
routeItem,
|
|
278
286
|
location
|
|
279
287
|
});
|
|
280
|
-
const currentLoaderState = loaderMap.get(
|
|
288
|
+
const currentLoaderState = loaderMap.get(path)?.state;
|
|
281
289
|
if (currentLoaderState) loaderStateRef.set(currentLoaderState);
|
|
282
290
|
} else {
|
|
283
|
-
const pendingShouldExist = routeItem?.loader && !isCacheItemFresh(
|
|
291
|
+
const pendingShouldExist = routeItem?.loader && !isCacheItemFresh(path);
|
|
284
292
|
pendingState.setState(pendingShouldExist ? {
|
|
285
293
|
routeItem,
|
|
286
294
|
location
|
|
287
295
|
} : void 0);
|
|
288
296
|
}
|
|
289
297
|
};
|
|
290
|
-
const
|
|
298
|
+
const polling = (routeItem, location) => {
|
|
291
299
|
if (!routeItem?.pollingInterval) return;
|
|
300
|
+
const signal = createSignal();
|
|
292
301
|
interval = window.setInterval(() => revalidateCache({
|
|
293
302
|
routeItem,
|
|
294
|
-
pathname: location.pathname
|
|
303
|
+
pathname: location.pathname,
|
|
304
|
+
search: location.search,
|
|
305
|
+
signal
|
|
295
306
|
}), routeItem.pollingInterval);
|
|
296
307
|
};
|
|
297
|
-
const loader = async (routeItem, location) => {
|
|
308
|
+
const loader = async (routeItem, location, seq) => {
|
|
298
309
|
if (!routeItem?.loader) return;
|
|
299
310
|
window.clearInterval(interval);
|
|
311
|
+
const signal = createSignal();
|
|
300
312
|
await revalidateCache({
|
|
301
313
|
routeItem,
|
|
302
314
|
pathname: location.pathname,
|
|
303
|
-
search: location.search
|
|
315
|
+
search: location.search,
|
|
316
|
+
signal
|
|
304
317
|
});
|
|
305
|
-
|
|
318
|
+
if (seq !== navigationSeq) return;
|
|
319
|
+
polling(routeItem, location);
|
|
306
320
|
};
|
|
307
321
|
const afterLoad = async (routeItem, params) => {
|
|
308
322
|
const { defaultAfterLoad } = routerConfig;
|
|
@@ -322,7 +336,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
322
336
|
await beforeLoad(nextItem, params);
|
|
323
337
|
if (seq !== navigationSeq) return;
|
|
324
338
|
prepareNavigation(nextItem, nextLocation);
|
|
325
|
-
await loader(nextItem, nextLocation);
|
|
339
|
+
await loader(nextItem, nextLocation, seq);
|
|
326
340
|
if (seq !== navigationSeq) return;
|
|
327
341
|
commitNavigation(nextLocation, nextItem);
|
|
328
342
|
await afterLoad(nextItem, params);
|
|
@@ -331,7 +345,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
331
345
|
};
|
|
332
346
|
//#endregion
|
|
333
347
|
//#region runtime/invalidate.ts
|
|
334
|
-
var redirect =
|
|
348
|
+
var redirect = Promise.resolve;
|
|
335
349
|
var createInvalidate = ({ routeItemDataState, loaderStateRef, loaderMap, currentLoaderState, contextState }, revalidateCache) => {
|
|
336
350
|
const invalidatePath = async (routeItem, pathname, options) => {
|
|
337
351
|
const routePathname = routeItemDataState.getState().location.pathname;
|
|
@@ -398,7 +412,6 @@ var createPrefetch = (revalidateCache) => async (pathname) => {
|
|
|
398
412
|
};
|
|
399
413
|
//#endregion
|
|
400
414
|
//#region utils/revalidateCache.ts
|
|
401
|
-
var loadingPromises = /* @__PURE__ */ new Map();
|
|
402
415
|
var isObjectRetry = (arg) => typeof arg === "object";
|
|
403
416
|
var createRetry = (arg) => {
|
|
404
417
|
if (arg === void 0) return null;
|
|
@@ -418,26 +431,44 @@ var getRetry = (routeItem) => {
|
|
|
418
431
|
};
|
|
419
432
|
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
420
433
|
var createRevalidateCache = (routerState) => {
|
|
421
|
-
const { loaderStateRef, contextState, loaderMap } = routerState;
|
|
422
|
-
const
|
|
423
|
-
const
|
|
434
|
+
const { loaderStateRef, contextState, loaderMap, loadingPromises } = routerState;
|
|
435
|
+
const removeStaleItems = () => {
|
|
436
|
+
const deletedItems = [...loaderMap.entries()].filter(([, item]) => {
|
|
424
437
|
const staleTime = item.staleTime ?? routerConfig.defaultStaleTime;
|
|
425
438
|
return staleTime && staleTime + item.timestamp < Date.now();
|
|
426
439
|
});
|
|
427
|
-
if (
|
|
440
|
+
if (deletedItems.length) deletedItems.forEach((item) => loaderMap.delete(item[0]));
|
|
428
441
|
};
|
|
429
|
-
const
|
|
442
|
+
const evict = () => {
|
|
443
|
+
if (loaderMap.size <= routerConfig.maxCacheSize) return;
|
|
444
|
+
const oldestKey = loaderMap.keys().next().value;
|
|
445
|
+
if (oldestKey) loaderMap.delete(oldestKey);
|
|
446
|
+
};
|
|
447
|
+
const moveItemToLastPosition = (path) => {
|
|
448
|
+
const item = loaderMap.get(path);
|
|
449
|
+
if (item) {
|
|
450
|
+
loaderMap.delete(path);
|
|
451
|
+
loaderMap.set(path, item);
|
|
452
|
+
}
|
|
453
|
+
return item;
|
|
454
|
+
};
|
|
455
|
+
const revalidateCache = async ({ routeItem, pathname, search = "", signal }, retried = 0) => {
|
|
430
456
|
if (!routeItem?.loader) return;
|
|
431
457
|
const isCacheItemFresh = createIsCacheItemFresh(loaderMap);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
458
|
+
removeStaleItems();
|
|
459
|
+
const path = `${pathname}${search}`;
|
|
460
|
+
if (loadingPromises.has(path)) {
|
|
461
|
+
moveItemToLastPosition(path);
|
|
462
|
+
return loadingPromises.get(path);
|
|
463
|
+
}
|
|
464
|
+
if (isCacheItemFresh(path)) {
|
|
465
|
+
const item = moveItemToLastPosition(path);
|
|
436
466
|
if (item?.state) loaderStateRef.set(item.state);
|
|
437
467
|
return;
|
|
438
468
|
}
|
|
439
469
|
const promise = (async () => {
|
|
440
470
|
if (!routeItem?.loader) return;
|
|
471
|
+
const effectiveSignal = signal ?? new AbortController().signal;
|
|
441
472
|
try {
|
|
442
473
|
const context = contextState.getState();
|
|
443
474
|
const setContext = contextState.setState;
|
|
@@ -447,31 +478,38 @@ var createRevalidateCache = (routerState) => {
|
|
|
447
478
|
params,
|
|
448
479
|
context,
|
|
449
480
|
setContext,
|
|
450
|
-
searchParams
|
|
481
|
+
searchParams,
|
|
482
|
+
signal: effectiveSignal
|
|
451
483
|
});
|
|
452
484
|
loaderStateRef.set((prev) => ({
|
|
453
485
|
...prev,
|
|
454
486
|
data: result,
|
|
455
487
|
loaderError: null
|
|
456
488
|
}));
|
|
457
|
-
loaderMap.set(
|
|
489
|
+
loaderMap.set(path, {
|
|
458
490
|
state: loaderStateRef.value,
|
|
459
491
|
timestamp: Date.now(),
|
|
460
492
|
staleTime: routeItem.staleTime
|
|
461
493
|
});
|
|
494
|
+
evict();
|
|
462
495
|
return {
|
|
463
496
|
data: result,
|
|
464
497
|
error: null
|
|
465
498
|
};
|
|
466
499
|
} catch (error) {
|
|
500
|
+
if (effectiveSignal.aborted) return {
|
|
501
|
+
data: null,
|
|
502
|
+
error: null
|
|
503
|
+
};
|
|
467
504
|
const retry = getRetry(routeItem);
|
|
468
505
|
if (retry && retry.count > retried) {
|
|
469
|
-
loadingPromises.delete(
|
|
506
|
+
loadingPromises.delete(path);
|
|
470
507
|
if (retry.delay) await sleep(retry.delay);
|
|
471
508
|
await revalidateCache({
|
|
472
509
|
routeItem,
|
|
473
510
|
pathname,
|
|
474
|
-
search
|
|
511
|
+
search,
|
|
512
|
+
signal
|
|
475
513
|
}, retried + 1);
|
|
476
514
|
return {
|
|
477
515
|
data: null,
|
|
@@ -489,10 +527,10 @@ var createRevalidateCache = (routerState) => {
|
|
|
489
527
|
};
|
|
490
528
|
}
|
|
491
529
|
} finally {
|
|
492
|
-
loadingPromises.delete(
|
|
530
|
+
loadingPromises.delete(path);
|
|
493
531
|
}
|
|
494
532
|
})();
|
|
495
|
-
loadingPromises.set(
|
|
533
|
+
loadingPromises.set(path, promise);
|
|
496
534
|
return promise;
|
|
497
535
|
};
|
|
498
536
|
return revalidateCache;
|
|
@@ -528,7 +566,8 @@ var createRouterInstance = () => {
|
|
|
528
566
|
to: ""
|
|
529
567
|
}),
|
|
530
568
|
loaderStateRef: new Cell(emptyLoaderState),
|
|
531
|
-
loaderMap: /* @__PURE__ */ new Map()
|
|
569
|
+
loaderMap: /* @__PURE__ */ new Map(),
|
|
570
|
+
loadingPromises: /* @__PURE__ */ new Map()
|
|
532
571
|
};
|
|
533
572
|
const revalidateCache = createRevalidateCache(routerState);
|
|
534
573
|
const invalidate = createInvalidate(routerState, revalidateCache);
|
|
@@ -716,7 +755,9 @@ var renderElement = (Component) => {
|
|
|
716
755
|
//#region components/Router.tsx
|
|
717
756
|
var EmptyBoundary = ({ children }) => children;
|
|
718
757
|
var IS_MOBILE = isMobile();
|
|
719
|
-
var
|
|
758
|
+
var MOBILE_CACHE_SIZE = 60;
|
|
759
|
+
var DESKTOP_CACHE_SIZE = 150;
|
|
760
|
+
var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated = false, spinner = true, defaultPreserveScroll = true, showFallbackOnAnimation = false, maxCacheSize = IS_MOBILE ? MOBILE_CACHE_SIZE : DESKTOP_CACHE_SIZE, defaultPrefetch = IS_MOBILE ? "viewport" : "hover", defaultHoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary }) => {
|
|
720
761
|
const { useRouteItemData, usePendingState } = router.hooks;
|
|
721
762
|
const [routeItemData] = useRouteItemData();
|
|
722
763
|
const [pendingState] = usePendingState();
|
|
@@ -732,7 +773,8 @@ var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration,
|
|
|
732
773
|
defaultAfterLoad,
|
|
733
774
|
defaultRetry,
|
|
734
775
|
defaultStaleTime,
|
|
735
|
-
defaultPreserveScroll
|
|
776
|
+
defaultPreserveScroll,
|
|
777
|
+
maxCacheSize
|
|
736
778
|
});
|
|
737
779
|
useApplyCustomAnimation(animationDuration);
|
|
738
780
|
useSetInitialContext(initialContext);
|
package/dist/types.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type ClientRouteItem = {
|
|
|
23
23
|
context: Record<string, unknown>;
|
|
24
24
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
25
25
|
searchParams: Record<string, string>;
|
|
26
|
+
signal: AbortSignal;
|
|
26
27
|
}): Promise<unknown>;
|
|
27
28
|
loaderFallback?: RenderElement;
|
|
28
29
|
errorElement?: RenderElement;
|
|
@@ -64,6 +65,7 @@ export type RevalidateCacheArgs = {
|
|
|
64
65
|
pathname: string;
|
|
65
66
|
routeItem?: RouteItem;
|
|
66
67
|
search?: string;
|
|
68
|
+
signal?: AbortSignal;
|
|
67
69
|
};
|
|
68
70
|
export type LoaderState<T = unknown> = {
|
|
69
71
|
data: T;
|
|
@@ -92,6 +94,7 @@ export type RouterProps = {
|
|
|
92
94
|
showFallbackOnAnimation?: boolean;
|
|
93
95
|
defaultPrefetch?: 'hover' | 'render' | 'viewport' | 'none';
|
|
94
96
|
defaultHoverPrefetchDelay?: number;
|
|
97
|
+
maxCacheSize?: number;
|
|
95
98
|
errorBoundary?: ComponentType<{
|
|
96
99
|
children: ReactNode;
|
|
97
100
|
}>;
|
|
@@ -104,6 +107,13 @@ export type LoaderStateItem = {
|
|
|
104
107
|
timestamp: number;
|
|
105
108
|
staleTime: number | undefined;
|
|
106
109
|
};
|
|
110
|
+
export type LoadingPromise = Promise<{
|
|
111
|
+
data: unknown;
|
|
112
|
+
error: null;
|
|
113
|
+
} | {
|
|
114
|
+
data: null;
|
|
115
|
+
error: unknown;
|
|
116
|
+
} | undefined>;
|
|
107
117
|
export type RouterState = {
|
|
108
118
|
routeItemDataState: Store<RouteItemData>;
|
|
109
119
|
pendingState: Store<RouteItemData | undefined>;
|
|
@@ -116,6 +126,7 @@ export type RouterState = {
|
|
|
116
126
|
}>;
|
|
117
127
|
loaderStateRef: Cell<LoaderState>;
|
|
118
128
|
loaderMap: Map<string, LoaderStateItem>;
|
|
129
|
+
loadingPromises: Map<string, LoadingPromise>;
|
|
119
130
|
};
|
|
120
131
|
export type RouterType = {
|
|
121
132
|
state: Omit<RouterState, 'timestampMap'>;
|
|
@@ -147,10 +158,7 @@ export type InvalidateOptions = {
|
|
|
147
158
|
withChildren?: boolean;
|
|
148
159
|
withBeforeLoad?: boolean;
|
|
149
160
|
};
|
|
150
|
-
export type RevalidateCache = (
|
|
151
|
-
data: unknown;
|
|
152
|
-
error: unknown;
|
|
153
|
-
}>;
|
|
161
|
+
export type RevalidateCache = (args: RevalidateCacheArgs) => LoadingPromise;
|
|
154
162
|
export type Options = Partial<{
|
|
155
163
|
onSuccess: (args: unknown) => void;
|
|
156
164
|
onError: (args: unknown) => void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { LoaderStateItem } from '../types';
|
|
2
|
-
export declare const createIsCacheItemFresh: (loaderMap: Map<string, LoaderStateItem>) => (
|
|
2
|
+
export declare const createIsCacheItemFresh: (loaderMap: Map<string, LoaderStateItem>) => (path: string) => boolean;
|
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import { RevalidateCacheArgs, RouterState } from '../types';
|
|
2
|
-
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search }: RevalidateCacheArgs, retried?: number) => Promise<
|
|
2
|
+
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search, signal }: RevalidateCacheArgs, retried?: number) => Promise<{
|
|
3
|
+
data: unknown;
|
|
4
|
+
error: null;
|
|
5
|
+
} | {
|
|
6
|
+
data: null;
|
|
7
|
+
error: unknown;
|
|
8
|
+
} | undefined>;
|