clear-react-router 1.7.6 → 1.7.7

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
@@ -396,13 +396,13 @@ const UserProfile = () => {
396
396
 
397
397
  ### `useInvalidate()`
398
398
 
399
- Returns a function that marks route data as stale and re-runs the route lifecycle.
399
+ Returns a function that revalidates cached route data by executing the route lifecycle again.
400
400
 
401
- Calling `invalidate()` clears the cached loader result for a route and executes both `beforeLoad` and `loader` again. This is useful after mutations or any operation that changes data used by the route.
401
+ Calling `invalidate()` clears the cached loader result and immediately runs both `beforeLoad` and `loader` for the specified route. This is useful after mutations or any operation that changes data used by the route.
402
402
 
403
403
  #### Current route
404
404
 
405
- Invalidate the currently active route:
405
+ Revalidate the currently active route:
406
406
 
407
407
  ```tsx
408
408
  const invalidate = useInvalidate();
@@ -412,41 +412,80 @@ await invalidate();
412
412
 
413
413
  #### Specific route
414
414
 
415
- You can also invalidate any registered route by passing its pathname:
415
+ Revalidate any registered route by passing its pathname:
416
416
 
417
417
  ```tsx
418
- const invalidate = useInvalidate();
419
-
420
418
  await invalidate('/posts');
421
419
  ```
422
420
 
423
- The route does not need to be currently active. Its cache will be marked as stale, and the next time it is visited, `beforeLoad` and `loader` will run again.
421
+ #### Multiple routes
424
422
 
425
- #### Why use it?
423
+ You can revalidate several routes at once by passing an array of pathnames:
426
424
 
427
- A common use case is refreshing route data after a mutation.
425
+ ```tsx
426
+ await invalidate([ '/posts', '/profile', '/settings' ]);
427
+ ```
428
428
 
429
- For example, after deleting a post while viewing `/posts/42`, you may want the posts list to be reloaded the next time the user navigates to `/posts`:
429
+ #### Dynamic routes
430
+
431
+ When a dynamic route pattern is provided, every cached route matching that pattern will be revalidated.
432
+
433
+ For example:
430
434
 
431
435
  ```tsx
432
- const invalidate = useInvalidate();
436
+ await invalidate('/post/[id]');
437
+ ```
433
438
 
434
- await deletePost(id);
435
- await invalidate('/posts');
439
+ will revalidate all cached routes such as:
440
+
441
+ ```text
442
+ /post/1
443
+ /post/17
444
+ /post/42
436
445
  ```
437
446
 
438
- Likewise, after updating the current page, you can immediately refresh its data:
447
+ This also works for nested dynamic routes:
439
448
 
440
449
  ```tsx
441
- const invalidate = useInvalidate();
450
+ await invalidate('/post/[id]/comment/[id]');
451
+ ```
442
452
 
443
- await updateProfile(data);
444
- await invalidate();
453
+ #### Including child routes
454
+
455
+ To revalidate a single route together with its cached child routes, pass the `withChildren` option:
456
+
457
+ ```tsx
458
+ await invalidate('/posts', { withChildren: true }); // a route, not a route list
459
+ ```
460
+
461
+ This will recursively revalidate cached routes inside the route tree.
462
+
463
+ For example, if the following routes have been visited:
464
+
465
+ ```text
466
+ /posts
467
+ /post/17
468
+ /post/23
469
+ /post/42/comments
445
470
  ```
446
471
 
472
+ then:
473
+
474
+ ```tsx
475
+ await invalidate('/posts', { withChildren: true });
476
+ ```
477
+
478
+ will revalidate the cached child routes:
479
+
480
+ ```text
481
+ /post/17
482
+ /post/23
483
+ /post/42/comments
484
+ ```
447
485
  #### Notes
448
486
 
449
487
  * `invalidate()` re-executes both `beforeLoad` and `loader` for the invalidated route.
488
+ * Only routes that already have cached data are revalidated.
450
489
  * Cached data is discarded before the new loader starts.
451
490
  * When used as an event handler, wrap the call in an arrow function:
452
491
 
@@ -1,4 +1,4 @@
1
1
  export declare const useGetAction: (actionKey: string) => {
2
2
  currentAction: (arg: FormData) => Promise<unknown> | Promise<void> | void | unknown;
3
- invalidate: (path?: string) => Promise<void>;
3
+ invalidate: typeof import("../runtime/invalidate").invalidate;
4
4
  };
@@ -1 +1,2 @@
1
- export declare const useInvalidate: () => (path?: string) => Promise<void>;
1
+ import { invalidate } from '../runtime/invalidate';
2
+ export declare const useInvalidate: () => typeof invalidate;
package/dist/index.js CHANGED
@@ -170,21 +170,28 @@ var createLazyComponent = (importFn, fallback) => {
170
170
  //#region utils/utils.ts
171
171
  var isLazy = (el) => typeof el.element === "function" && el.element.toString().includes("import(");
172
172
  var parseClientRouteItem = (el, parentParams = [], parentPath = "") => {
173
- const currentParamsList = el.path.match(/:[^/]+/g);
174
- const normalizedSplitPath = el.path.replaceAll(/:[^/]+(\/|$)/g, "").split("/").filter(Boolean);
175
- const splitPath = el.path.split("/");
176
- const currentParams = currentParamsList ? [...parentParams, ...currentParamsList.map((param, index) => ({
177
- key: normalizedSplitPath[index],
178
- value: param.slice(1)
179
- }))] : parentParams;
180
- const path = currentParams.length ? `${parentPath}${splitPath.slice(0, splitPath.length - 1).join("/")}` : el.path;
173
+ const segments = el.path.split("/").filter(Boolean);
174
+ const staticSegments = [];
175
+ const currentParams = [...parentParams];
176
+ let lastStaticSegment = "";
177
+ for (const segment of segments) if (segment.startsWith(":")) {
178
+ if (!lastStaticSegment) throw new Error(`Route "${el.path}" cannot start with a parameter.`);
179
+ currentParams.push({
180
+ key: lastStaticSegment,
181
+ value: segment.slice(1)
182
+ });
183
+ } else {
184
+ lastStaticSegment = segment;
185
+ staticSegments.push(segment);
186
+ }
187
+ const path = `${parentPath}/${staticSegments.join("/")}`.replace(/\/+/g, "/");
181
188
  const resolvedElement = isLazy(el) ? createLazyComponent(el.element, el.fallback) : el.element;
182
189
  return [{
183
190
  ...el,
184
191
  path,
185
192
  params: currentParams,
186
193
  element: resolvedElement
187
- }, ...el.children?.flatMap((child) => parseClientRouteItem(child, currentParams, path)) || []];
194
+ }, ...el.children?.flatMap((child) => parseClientRouteItem(child, currentParams, path)) ?? []];
188
195
  };
189
196
  var createRouter = (clientList) => clientList.flatMap((el) => parseClientRouteItem(el, []));
190
197
  var getParamsObject = ({ params, pathname }) => {
@@ -625,22 +632,21 @@ var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_js
625
632
  });
626
633
  //#endregion
627
634
  //#region runtime/invalidate.ts
628
- var invalidate = async (path) => {
635
+ var redirect = () => Promise.resolve();
636
+ var invalidatePath = async (routeItem, pathname) => {
629
637
  const routePathname = routeItemDataState.getState().location.pathname;
630
- const pathname = path || routePathname;
631
- const routeItem = findRoute(pathname);
632
- const resultParams = getParamsObject({
638
+ timestampMap.delete(pathname);
639
+ const params = getParamsObject({
633
640
  params: routeItem?.params,
634
641
  pathname
635
642
  });
636
- timestampMap.delete(pathname);
637
643
  try {
638
644
  if (routeItem?.beforeLoad) {
639
645
  const { context, setContext } = getContext();
640
646
  await routeItem.beforeLoad({
641
647
  context,
642
- redirect: () => Promise.resolve(),
643
- params: resultParams,
648
+ redirect,
649
+ params,
644
650
  setContext
645
651
  });
646
652
  }
@@ -660,6 +666,22 @@ var invalidate = async (path) => {
660
666
  });
661
667
  if (pathname === routePathname) currentLoaderState.setState(loaderStateRef.value);
662
668
  };
669
+ var invalidateItem = async (pathname, withChildren) => {
670
+ const routeItem = findRoute(pathname);
671
+ if (!routeItem) return;
672
+ const pathnameArray = [];
673
+ for (const [key] of timestampMap) if (comparePaths(routeItem, key)) pathnameArray.push(key);
674
+ await Promise.all(pathnameArray.map((pathname) => invalidatePath(routeItem, pathname)));
675
+ if (withChildren && routeItem.children?.length) {
676
+ const childPathList = routeItem.children.map((el) => el.path);
677
+ await Promise.all(childPathList.map((el) => invalidateItem(`${pathname}${el}`, withChildren)));
678
+ }
679
+ };
680
+ async function invalidate(pathList, options) {
681
+ const routePathname = routeItemDataState.getState().location.pathname;
682
+ const pathnameList = Array.isArray(pathList) ? pathList : pathList ? [pathList] : [routePathname];
683
+ await Promise.all(pathnameList.map((pathname) => invalidateItem(pathname, options?.withChildren)));
684
+ }
663
685
  //#endregion
664
686
  //#region hooks/useInvalidate.ts
665
687
  var useInvalidate = () => invalidate;
@@ -1 +1,6 @@
1
- export declare const invalidate: (path?: string) => Promise<void>;
1
+ type Options = {
2
+ withChildren?: boolean;
3
+ };
4
+ export declare function invalidate(path?: string[]): Promise<void>;
5
+ export declare function invalidate(path?: string, options?: Options): Promise<void>;
6
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.7.6",
3
+ "version": "1.7.7",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {