clear-react-router 1.9.0 → 1.9.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 CHANGED
@@ -645,122 +645,6 @@ useEffect(() => {
645
645
  }, [state, process, reset]);
646
646
  ```
647
647
 
648
- ### `useQueryParam()`
649
-
650
- A flexible hook for working with typed query parameters. You provide an adapter object with `parse` and `serialize` functions, and it returns the parsed value and a setter.
651
-
652
- ```tsx
653
- import { useQueryParam, adapter } from 'clear-react-router';
654
-
655
- const ProductPage = () => {
656
- // String parameter
657
- const [brand, setBrand] = useQueryParam('brand', adapter.string, 'nike');
658
-
659
- // Number parameter
660
- const [page, setPage] = useQueryParam('page', adapter.integer, 1);
661
-
662
- // Date parameter
663
- const [date, setDate] = useQueryParam('date', adapter.date, new Date());
664
-
665
- // Array of numbers
666
- const [prices, setPrices] = useQueryParam('prices', adapter.floatArray);
667
-
668
- return (
669
- <div>
670
- <p>Brand: {brand}</p>
671
- <p>Page: {page}</p>
672
- <button onClick={() => setPage(page + 1)}>Next</button>
673
- </div>
674
- );
675
- }
676
- ```
677
- type Adapter<T> = {
678
- parse: (params: string[]) => T;
679
- serialize?: (params: T) => string | string[];
680
- }
681
-
682
- **Signature:** `useQueryParam<T>(field: string, adapter: Adapter<T>, defaultValue?: T): [T, (arg: T | null) => void]`
683
-
684
- | Argument | Type | Description |
685
- |----------|------|-------------|
686
- | `field` | `string` | The query parameter key (e.g., `'page'`, `'brand'`) |
687
- | `adapter` | `Adapter<T>` | Parser and optional serializer for params (serializer String is used in case of serializer not passed) |
688
- | `defaultValue` | `T` (optional) | Default value returned when the parameter is missing or empty |
689
-
690
- **Returns:**
691
-
692
- | Element | Type | Description |
693
- |---------|------|-------------|
694
- | `value` | `T` | The parsed value from the query parameter |
695
- | `setValue` | `(arg: T \| null) => void` | Function to update the query parameter. Null is passed to remove the parameter. |
696
-
697
- ### Built-in Adapters
698
-
699
- | Adapter | Input | Output | Description |
700
- |---------|-------|--------|-------------|
701
- | `adapter.string` | `string[]` | `string` | First value or empty string |
702
- | `adapter.stringArray` | `string[]` | `string[]` | All values as array |
703
- | `adapter.integer` | `string[]` | `number` | First value parsed as integer (default: `0`) |
704
- | `adapter.integerArray` | `string[]` | `number[]` | All values parsed as integers |
705
- | `adapter.float` | `string[]` | `number` | First value parsed as float (default: `0`) |
706
- | `adapter.floatArray` | `string[]` | `number[]` | All values parsed as floats |
707
- | `adapter.boolean` | `string[]` | `boolean` | First value parsed as boolean (`'true'` → `true`) |
708
- | `adapter.booleanArray` | `string[]` | `boolean[]` | All values parsed as booleans |
709
- | `adapter.date` | `string[]` | `Date` | First value parsed as Date from timestamp |
710
- | `adapter.dateArray` | `string[]` | `Date[]` | All values parsed as Dates from timestamps |
711
- | `adapter.zodSchema` | `string[]` | `T` | Validates JSON string against Zod schema |
712
-
713
- ### Using Zod Schemas
714
- `useQueryParam` works seamlessly with Zod for complex validation:
715
-
716
- ```tsx
717
- import { z } from 'zod';
718
- import { useQueryParam, adapter } from 'clear-react-router';
719
-
720
- const filterSchema = z.object({
721
- name: z.string(),
722
- age: z.number().min(0),
723
- active: z.boolean().optional(),
724
- });
725
-
726
- function ProductFilter() {
727
- const [filter, setFilter] = useQueryParam(
728
- 'filter',
729
- adapter.zodSchema(filterSchema),
730
- { name: '', age: 0 }
731
- );
732
-
733
- return (
734
- <div>
735
- <p>Name: {filter.name}</p>
736
- <p>Age: {filter.age}</p>
737
- <button onClick={() => setFilter({ ...filter, age: filter.age + 1 })}>
738
- Increment Age
739
- </button>
740
- </div>
741
- );
742
- }
743
- ```
744
-
745
- ### Custom Adapters
746
- You can write your own adapter for any format:
747
-
748
- ```tsx
749
- // Custom adapter for comma-separated values
750
- const csvAdapter = {
751
- parse: (params: string[]): string[] => {
752
- const value = params[0] || '';
753
- return value ? value.split(',').map(v => v.trim()) : [];
754
- },
755
- serialize: (value: string[]): string[] => value
756
- }
757
-
758
- const TagsFilter() {
759
- const [tags, setTags] = useQueryParam('tags', csvAdapter, []);
760
- // tags: string[]
761
- }
762
- ```
763
-
764
648
  ### `useRouterContext()`
765
649
 
766
650
  Returns the router context object and a function to update it. Useful for accessing or modifying global state (like user authentication, theme, etc.) from anywhere in your app.
package/dist/cell.d.ts CHANGED
@@ -4,6 +4,3 @@ export declare class Cell<T> {
4
4
  get value(): T;
5
5
  set(action: T | ((prev: T) => T)): void;
6
6
  }
7
- export declare const loaderStateRef: Cell<import("./types").LoaderState>;
8
- export declare const prevPathnameRef: Cell<string>;
9
- export declare const timestampMap: Map<string, number>;
@@ -3,7 +3,6 @@ declare class RouterConfig {
3
3
  routes: RouterProps['routes'];
4
4
  prefetch: RouterProps['prefetch'];
5
5
  isAnimated: RouterProps['isAnimated'];
6
- showFallbackOnAnimation: RouterProps['showFallbackOnAnimation'];
7
6
  hoverPrefetchDelay: number;
8
7
  beforeLoad?: ClientRouteItem['beforeLoad'];
9
8
  afterLoad?: ClientRouteItem['afterLoad'];
package/dist/index.d.ts CHANGED
@@ -9,9 +9,7 @@ export { useInvalidate } from './hooks/useInvalidate';
9
9
  export { useBlocker } from './hooks/useBlocker';
10
10
  export { useAction } from './hooks/useAction';
11
11
  export { useRouterContext } from './hooks/useRouterContext';
12
- export { useQueryParam } from './hooks/useQueryParam';
13
12
  export { useSearchParams } from './hooks/useSearchParams';
14
13
  export { useFormContext } from './hooks/useFormContext';
15
- export { adapter } from './utils/adapter';
16
14
  export { createRouter } from './utils/utils';
17
- export type { RouteItem, BlockerState, Location, Adapter, RouterProps } from './types';
15
+ export type { RouteItem, BlockerState, Location, RouterProps } from './types';
package/dist/index.js CHANGED
@@ -30,15 +30,12 @@ var useGlobalState = ({ subscribe, getState, setState }) => {
30
30
  };
31
31
  //#endregion
32
32
  //#region utils/commitState.ts
33
- var createCommitState = ({ isLoadingState, routeItemDataState, loaderFallbackState, currentLoaderState, prevPathnameRef, loaderStateRef }) => (nextLocation, routeItem) => {
33
+ var createCommitState = ({ routeItemDataState, pendingState }) => (nextLocation, routeItem) => {
34
34
  routeItemDataState.setState({
35
35
  routeItem,
36
36
  location: nextLocation
37
37
  });
38
- currentLoaderState.setState(loaderStateRef.value);
39
- isLoadingState.setState(false);
40
- loaderFallbackState.setState(void 0);
41
- prevPathnameRef.set(nextLocation.pathname);
38
+ pendingState.setState(void 0);
42
39
  const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
43
40
  if (fullPath === window.location.pathname + window.location.search) return;
44
41
  history.pushState(null, "", fullPath);
@@ -91,7 +88,6 @@ var RouterConfig = class {
91
88
  _defineProperty(this, "routes", []);
92
89
  _defineProperty(this, "prefetch", "hover");
93
90
  _defineProperty(this, "isAnimated", false);
94
- _defineProperty(this, "showFallbackOnAnimation", false);
95
91
  _defineProperty(this, "hoverPrefetchDelay", 150);
96
92
  _defineProperty(this, "beforeLoad", void 0);
97
93
  _defineProperty(this, "afterLoad", void 0);
@@ -106,12 +102,9 @@ var RouterConfig = class {
106
102
  var routerConfig = new RouterConfig();
107
103
  //#endregion
108
104
  //#region utils/commitNavigation.ts
109
- var createCommitNavigation = (navigationExecutor, prevPathnameRef) => (nextLocation, routeItem) => {
110
- const { isAnimated } = routerConfig;
111
- if (!isAnimated || !prevPathnameRef.value) {
112
- navigationExecutor(nextLocation, routeItem);
113
- return;
114
- }
105
+ var createCommitNavigation = (navigationExecutor, routeItemDataState) => (nextLocation, routeItem) => {
106
+ const isFirstLoad = !routeItemDataState.getState().location.pathname;
107
+ if (!routerConfig.isAnimated || isFirstLoad) return navigationExecutor(nextLocation, routeItem);
115
108
  try {
116
109
  document.startViewTransition(() => navigationExecutor(nextLocation, routeItem));
117
110
  } catch {
@@ -223,8 +216,8 @@ var findRoute = (pathname, includeAll) => {
223
216
  var navigationSeq = 0;
224
217
  var interval = 0;
225
218
  var createNavigate = (routerState, revalidateCache) => {
226
- const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap, pendingPathRef, routeItemDataState } = routerState;
227
- const commitNavigation = createCommitNavigation(createCommitState(routerState), prevPathnameRef);
219
+ const { loaderStateRef, scrollMapState, pendingState, contextState, timestampMap, routeItemDataState } = routerState;
220
+ const commitNavigation = createCommitNavigation(createCommitState(routerState), routeItemDataState);
228
221
  const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
229
222
  const getContext = () => ({
230
223
  context: contextState.getState(),
@@ -263,27 +256,25 @@ var createNavigate = (routerState, revalidateCache) => {
263
256
  if (routeItem?.beforeLoad) await runBeforeLoad(routeItem?.beforeLoad);
264
257
  };
265
258
  const prepareNavigation = (routeItem, location) => {
266
- const { isAnimated, showFallbackOnAnimation: showFallback } = routerConfig;
267
259
  scrollMapState.setState((prevState) => {
268
260
  const scrollPosition = document.scrollingElement?.scrollTop ?? 0;
269
- if (!scrollPosition || prevState[prevPathnameRef.value] === scrollPosition) return prevState;
261
+ const prevPathname = routeItemDataState.getState().location.pathname;
262
+ if (!scrollPosition || prevState[prevPathname] === scrollPosition) return prevState;
270
263
  return {
271
264
  ...prevState,
272
- [prevPathnameRef.value]: scrollPosition
265
+ [prevPathname]: scrollPosition
273
266
  };
274
267
  });
275
- loaderFallbackState.setState(isCacheItemFresh({
268
+ const pendingShouldExist = routeItem?.loader && !isCacheItemFresh({
276
269
  routeItem,
277
270
  pathname: location.pathname
278
- }) || isAnimated && !showFallback ? void 0 : routeItem?.loaderFallback);
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);
271
+ });
272
+ pendingState.setState(pendingShouldExist ? {
273
+ routeItem,
274
+ location
275
+ } : void 0);
284
276
  };
285
277
  const afterEachLoad = (routeItem) => {
286
- pendingPathRef.set("");
287
278
  if (!routeItem?.pollingInterval) return;
288
279
  interval = window.setInterval(() => revalidateCache({
289
280
  routeItem,
@@ -292,7 +283,7 @@ var createNavigate = (routerState, revalidateCache) => {
292
283
  };
293
284
  const loader = async (routeItem, location) => {
294
285
  if (!routeItem?.loader) return;
295
- beforeEachLoad(location);
286
+ window.clearInterval(interval);
296
287
  await revalidateCache({
297
288
  routeItem,
298
289
  pathname: location.pathname,
@@ -498,18 +489,15 @@ var Cell = class {
498
489
  this._value = typeof action === "function" ? action(this._value) : action;
499
490
  }
500
491
  };
501
- new Cell(emptyLoaderState);
502
- new Cell("");
503
492
  //#endregion
504
493
  //#region utils/createRouterInstance.ts
505
494
  var createRouterInstance = () => {
506
495
  const routerState = {
507
- isLoadingState: create(false),
508
- loaderFallbackState: create(void 0),
509
496
  routeItemDataState: create({
510
497
  routeItem: void 0,
511
498
  location: {}
512
499
  }),
500
+ pendingState: create(void 0),
513
501
  currentLoaderState: create(emptyLoaderState),
514
502
  scrollMapState: create({}),
515
503
  contextState: create({}),
@@ -518,8 +506,6 @@ var createRouterInstance = () => {
518
506
  to: ""
519
507
  }),
520
508
  loaderStateRef: new Cell(emptyLoaderState),
521
- prevPathnameRef: new Cell(""),
522
- pendingPathRef: new Cell(""),
523
509
  timestampMap: /* @__PURE__ */ new Map()
524
510
  };
525
511
  const revalidateCache = createRevalidateCache(routerState);
@@ -546,29 +532,17 @@ var createRouterInstance = () => {
546
532
  };
547
533
  };
548
534
  return {
549
- state: {
550
- isLoadingState: routerState.isLoadingState,
551
- loaderFallbackState: routerState.loaderFallbackState,
552
- routeItemDataState: routerState.routeItemDataState,
553
- currentLoaderState: routerState.currentLoaderState,
554
- scrollMapState: routerState.scrollMapState,
555
- contextState: routerState.contextState,
556
- blockedRouteState: routerState.blockedRouteState,
557
- prevPathnameRef: routerState.prevPathnameRef,
558
- pendingPathRef: routerState.pendingPathRef
559
- },
535
+ state: routerState,
560
536
  runtime: {
561
537
  navigate,
562
538
  invalidate,
563
539
  prefetch
564
540
  },
565
541
  hooks: {
566
- useIsLoading: () => useGlobalState(routerState.isLoadingState),
567
542
  useBlockedRoute: () => useGlobalState(routerState.blockedRouteState),
568
- useLoaderFallback: () => useGlobalState(routerState.loaderFallbackState),
569
543
  useRouteItemData: () => useGlobalState(routerState.routeItemDataState),
570
- useCurrentLoaderState: () => useGlobalState(routerState.currentLoaderState),
571
544
  useScrollMap: () => useGlobalState(routerState.scrollMapState),
545
+ usePendingState: () => useGlobalState(routerState.pendingState),
572
546
  useContextState: () => useGlobalState(routerState.contextState),
573
547
  useParams: () => getParamsObject(),
574
548
  useNavigate: () => {
@@ -621,16 +595,16 @@ var router = createRouterInstance();
621
595
  //#endregion
622
596
  //#region hooks/useNavigation.ts
623
597
  var useNavigation = () => {
624
- const { state: { prevPathnameRef, blockedRouteState }, runtime: { navigate } } = router;
598
+ const { state: { routeItemDataState, blockedRouteState }, runtime: { navigate } } = router;
625
599
  useEffect(() => {
626
600
  const handler = async (event) => {
627
601
  const newLocation = parseWindowLocation(event.target.location);
628
- if (prevPathnameRef.value === blockedRouteState.getState().from) {
602
+ if (routeItemDataState.getState().location.pathname === blockedRouteState.getState().from) {
629
603
  blockedRouteState.setState({
630
- from: prevPathnameRef.value,
604
+ from: routeItemDataState.getState().location.pathname,
631
605
  to: newLocation.pathname
632
606
  });
633
- history.pushState(null, "", prevPathnameRef.value);
607
+ history.pushState(null, "", routeItemDataState.getState().location.pathname);
634
608
  } else navigate(newLocation);
635
609
  };
636
610
  window.addEventListener("popstate", handler);
@@ -638,13 +612,11 @@ var useNavigation = () => {
638
612
  }, [
639
613
  blockedRouteState,
640
614
  navigate,
641
- prevPathnameRef.value
615
+ routeItemDataState
642
616
  ]);
643
617
  useEffect(() => {
644
- const currentLocation = parseWindowLocation(window.location);
645
- navigate(currentLocation);
646
- prevPathnameRef.set(currentLocation.pathname);
647
- }, [navigate, prevPathnameRef]);
618
+ navigate(parseWindowLocation(window.location));
619
+ }, [navigate]);
648
620
  };
649
621
  //#endregion
650
622
  //#region hooks/useApplyCustomAnimation.ts
@@ -722,18 +694,17 @@ var renderElement = (Component) => {
722
694
  //#region components/Router.tsx
723
695
  var EmptyBoundary = ({ children }) => children;
724
696
  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 }) => {
725
- const { useIsLoading, useLoaderFallback, useRouteItemData, useCurrentLoaderState } = router.hooks;
726
- const [isLoading] = useIsLoading();
727
- const [currentLoaderFallback] = useLoaderFallback();
697
+ const { useRouteItemData, usePendingState } = router.hooks;
728
698
  const [routeItemData] = useRouteItemData();
729
- const [loaderState] = useCurrentLoaderState();
699
+ const [pendingState] = usePendingState();
700
+ const loaderState = router.state.loaderStateRef.value;
701
+ const isLoading = Boolean(pendingState);
730
702
  useNavigation();
731
703
  useSetRouterConfig({
732
704
  routes,
733
705
  isAnimated,
734
706
  prefetch,
735
707
  hoverPrefetchDelay,
736
- showFallbackOnAnimation,
737
708
  beforeLoad,
738
709
  afterLoad,
739
710
  defaultRetry,
@@ -747,7 +718,7 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
747
718
  const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
748
719
  const showSpinner = spinner && isAnimated && isLoading;
749
720
  const loadingContent = !showErrorElement && isLoading;
750
- if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(currentLoaderFallback || defaultLoaderFallback);
721
+ if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(pendingState?.routeItem?.loaderFallback || defaultLoaderFallback);
751
722
  if (!showFallbackOnAnimation && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
752
723
  if (!routeItem) return null;
753
724
  if (showErrorElement) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [renderElement(routeItem.errorElement || defaultErrorElement), showSpinner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})] });
@@ -759,9 +730,9 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
759
730
  //#endregion
760
731
  //#region hooks/useIsRoutePending.ts
761
732
  var useIsRoutePending = (routePath) => {
762
- const { hooks: { useIsLoading }, state: { pendingPathRef } } = router;
763
- const [isPending] = useIsLoading();
764
- return isPending && pendingPathRef.value === routePath;
733
+ const { usePendingState } = router.hooks;
734
+ const [pendingState] = usePendingState();
735
+ return pendingState?.location.pathname === routePath;
765
736
  };
766
737
  //#endregion
767
738
  //#region hooks/useNavigate.ts
@@ -888,10 +859,7 @@ var Form = ({ children, action, onSuccess, onError, autoReset = true }) => {
888
859
  var useParams = router.hooks.useParams;
889
860
  //#endregion
890
861
  //#region hooks/useLoaderState.ts
891
- var useLoaderState = () => {
892
- const [loaderState] = router.hooks.useCurrentLoaderState();
893
- return loaderState;
894
- };
862
+ var useLoaderState = () => router.state.loaderStateRef.value;
895
863
  //#endregion
896
864
  //#region hooks/useInvalidate.ts
897
865
  var useInvalidate = () => router.runtime.invalidate;
@@ -981,19 +949,9 @@ var useSearch = () => {
981
949
  return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
982
950
  };
983
951
  //#endregion
984
- //#region hooks/useLatest.ts
985
- var useLatest = (value) => {
986
- const ref = useRef(value);
987
- useEffect(() => {
988
- ref.current = value;
989
- }, [value]);
990
- return ref;
991
- };
992
- //#endregion
993
952
  //#region hooks/useSearchParams.ts
994
953
  var useSearchParams = () => {
995
954
  const search = useSearch();
996
- const searchRef = useLatest(search);
997
955
  const searchString = search ? search.replace("?", "") : window.location.pathname.split("?")?.[1] ?? "";
998
956
  const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]);
999
957
  const getSearchParams = useCallback((param) => {
@@ -1010,105 +968,18 @@ var useSearchParams = () => {
1010
968
  searchParams,
1011
969
  getSearchParams,
1012
970
  setSearchParams: useCallback((param, value) => {
1013
- const currentParams = new URLSearchParams(searchRef.current);
971
+ const currentParams = new URLSearchParams(search);
1014
972
  if (typeof param === "string" && value !== void 0) {
1015
973
  currentParams.delete(param);
1016
974
  (Array.isArray(value) ? value : [value]).forEach((v) => currentParams.append(param, v));
1017
975
  navigateWithSearchParams(currentParams);
1018
976
  } else if (typeof param === "function") navigateWithSearchParams(param(currentParams));
1019
977
  else throw new Error("useSearchParams first argument must be either function or string");
1020
- }, [navigateWithSearchParams, searchRef])
978
+ }, [navigateWithSearchParams, search])
1021
979
  };
1022
980
  };
1023
981
  //#endregion
1024
- //#region hooks/useQueryParam.ts
1025
- function useQueryParam(field, adapter, defaultValue) {
1026
- const { searchParams, setSearchParams } = useSearchParams();
1027
- return [useMemo(() => {
1028
- const params = searchParams.getAll(field);
1029
- const result = adapter.parse(params);
1030
- const isValid = !(result instanceof Date) || result instanceof Date && !isNaN(result.getTime());
1031
- if (result !== void 0 && result !== null && result !== "" && isValid) return result;
1032
- if (defaultValue !== void 0) return defaultValue;
1033
- return result;
1034
- }, [
1035
- field,
1036
- adapter,
1037
- searchParams,
1038
- defaultValue
1039
- ]), useCallback((value) => {
1040
- if (!value) return setSearchParams(field, []);
1041
- setSearchParams(field, (adapter.serialize || String)(value));
1042
- }, [
1043
- field,
1044
- adapter.serialize,
1045
- setSearchParams
1046
- ])];
1047
- }
1048
- //#endregion
1049
982
  //#region hooks/useFormContext.ts
1050
983
  var useFormContext = () => useContext(FormContext);
1051
984
  //#endregion
1052
- //#region utils/adapter.ts
1053
- var adapter = {
1054
- string: { parse: (params) => params[0] || "" },
1055
- stringArray: {
1056
- parse: (params) => params,
1057
- serialize: (value) => value
1058
- },
1059
- integer: { parse: (params) => {
1060
- const result = parseInt(params[0] || "");
1061
- return isNaN(result) ? 0 : result;
1062
- } },
1063
- integerArray: {
1064
- parse: (params) => params.map((el) => {
1065
- const result = parseInt(el);
1066
- return isNaN(result) ? 0 : result;
1067
- }),
1068
- serialize: (value) => value.map(String)
1069
- },
1070
- float: { parse: (params) => {
1071
- const result = parseFloat(params[0] || "");
1072
- return isNaN(result) ? 0 : result;
1073
- } },
1074
- floatArray: {
1075
- parse: (params) => params.map((el) => {
1076
- const result = parseFloat(el);
1077
- return isNaN(result) ? 0 : result;
1078
- }),
1079
- serialize: (value) => value.map(String)
1080
- },
1081
- boolean: {
1082
- parse: (params) => params[0]?.toLowerCase() === "true",
1083
- serialize: String
1084
- },
1085
- booleanArray: {
1086
- parse: (params) => params.map((el) => el.toLowerCase() === "true"),
1087
- serialize: (value) => value.map(String)
1088
- },
1089
- date: {
1090
- parse: (params) => new Date(Number(params[0])),
1091
- serialize: (arg) => String(arg.getTime())
1092
- },
1093
- dateArray: {
1094
- parse: (params) => params.map((param) => new Date(Number(param))),
1095
- serialize: (args) => args.map((arg) => String(arg.getTime()))
1096
- },
1097
- zodSchema: (schema) => ({
1098
- parse: (params) => {
1099
- let parsed;
1100
- try {
1101
- parsed = params[0] ? JSON.parse(params[0]) : void 0;
1102
- } catch {
1103
- throw new Error("Invalid JSON");
1104
- }
1105
- if (parsed === void 0) return void 0;
1106
- const result = schema.safeParse(parsed);
1107
- if (!result.success) throw new Error("Invalid schema");
1108
- return result.data;
1109
- },
1110
- serialize: JSON.stringify
1111
- })
1112
- };
1113
- //#endregion
1114
- export { Form, Link, Router, adapter, createRouter, useAction, useBlocker, useFormContext, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
985
+ export { Form, Link, Router, createRouter, useAction, useBlocker, useFormContext, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useRouterContext, useSearchParams };
package/dist/types.d.ts CHANGED
@@ -65,10 +65,6 @@ export type LoaderState<T = unknown> = {
65
65
  loaderError: Error | null;
66
66
  beforeLoadError: Error | null;
67
67
  };
68
- export type Adapter<T> = {
69
- parse: (params: string[]) => T;
70
- serialize?: (params: T) => string | string[];
71
- };
72
68
  export type RouteItemData = {
73
69
  location: Location;
74
70
  routeItem: RouteItem | undefined;
@@ -99,9 +95,8 @@ export type RouterProps = {
99
95
  context?: Record<string, unknown>;
100
96
  };
101
97
  export type RouterState = {
102
- isLoadingState: Store<boolean>;
103
- loaderFallbackState: Store<RouteItem['loaderFallback']>;
104
98
  routeItemDataState: Store<RouteItemData>;
99
+ pendingState: Store<RouteItemData | undefined>;
105
100
  currentLoaderState: Store<LoaderState>;
106
101
  scrollMapState: Store<Record<string, number>>;
107
102
  contextState: Store<Record<string, unknown>>;
@@ -110,27 +105,23 @@ export type RouterState = {
110
105
  to: string;
111
106
  }>;
112
107
  loaderStateRef: Cell<LoaderState>;
113
- prevPathnameRef: Cell<string>;
114
- pendingPathRef: Cell<string>;
115
108
  timestampMap: Map<string, number>;
116
109
  };
117
110
  export type RouterType = {
118
- state: Omit<RouterState, 'loaderStateRef' | 'timestampMap'>;
111
+ state: Omit<RouterState, 'timestampMap'>;
119
112
  runtime: {
120
113
  navigate(arg: Location): Promise<void>;
121
114
  invalidate(pathList?: string | string[], options?: InvalidateOptions): Promise<InvalidateResult[]>;
122
115
  prefetch(pathname: string): Promise<void>;
123
116
  };
124
117
  hooks: {
125
- useIsLoading: () => ReturnType<typeof useGlobalState<boolean>>;
126
118
  useBlockedRoute: () => ReturnType<typeof useGlobalState<{
127
119
  from: string;
128
120
  to: string;
129
121
  }>>;
130
- useLoaderFallback: () => ReturnType<typeof useGlobalState<RenderElement | undefined>>;
131
122
  useRouteItemData: () => ReturnType<typeof useGlobalState<RouteItemData>>;
132
- useCurrentLoaderState: () => ReturnType<typeof useGlobalState<LoaderState>>;
133
123
  useScrollMap: () => ReturnType<typeof useGlobalState<Record<string, number>>>;
124
+ usePendingState: () => ReturnType<typeof useGlobalState<RouteItemData | undefined>>;
134
125
  useContextState: () => ReturnType<typeof useGlobalState<Record<string, unknown>>>;
135
126
  useParams: <T>() => T;
136
127
  useNavigate: () => (arg: Location | string | -1) => Promise<void>;
@@ -1,3 +1,3 @@
1
- import { Cell } from '../cell';
2
- import { Location, RouteItem } from '../types';
3
- export declare const createCommitNavigation: (navigationExecutor: (arg: Location, routeItem: RouteItem | undefined) => void, prevPathnameRef: Cell<string>) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
1
+ import { Store } from '../create';
2
+ import { Location, RouteItem, RouteItemData } from '../types';
3
+ export declare const createCommitNavigation: (navigationExecutor: (arg: Location, routeItem: RouteItem | undefined) => void, routeItemDataState: Store<RouteItemData>) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
@@ -1,2 +1,2 @@
1
1
  import { Location, RouteItem, RouterState } from '../types';
2
- export declare const createCommitState: ({ isLoadingState, routeItemDataState, loaderFallbackState, currentLoaderState, prevPathnameRef, loaderStateRef, }: RouterState) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
2
+ export declare const createCommitState: ({ routeItemDataState, pendingState }: RouterState) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -1 +0,0 @@
1
- export declare const useLatest: <T>(value: T) => import("react").RefObject<T>;
@@ -1,2 +0,0 @@
1
- import { Adapter } from '../types';
2
- export declare function useQueryParam<T>(field: string, adapter: Adapter<T>, defaultValue?: T): [T, (arg: T | null) => void];
@@ -1,51 +0,0 @@
1
- import { Adapter } from '../types';
2
- type ZodInterface<T> = {
3
- safeParse(input: unknown): {
4
- success: true;
5
- data: T;
6
- } | {
7
- success: false;
8
- error: unknown;
9
- };
10
- };
11
- export declare const adapter: {
12
- string: {
13
- parse: (params: string[]) => string;
14
- };
15
- stringArray: {
16
- parse: (params: string[]) => string[];
17
- serialize: (value: string[]) => string[];
18
- };
19
- integer: {
20
- parse: (params: string[]) => number;
21
- };
22
- integerArray: {
23
- parse: (params: string[]) => number[];
24
- serialize: (value: number[]) => string[];
25
- };
26
- float: {
27
- parse: (params: string[]) => number;
28
- };
29
- floatArray: {
30
- parse: (params: string[]) => number[];
31
- serialize: (value: number[]) => string[];
32
- };
33
- boolean: {
34
- parse: (params: string[]) => boolean;
35
- serialize: StringConstructor;
36
- };
37
- booleanArray: {
38
- parse: (params: string[]) => boolean[];
39
- serialize: (value: boolean[]) => string[];
40
- };
41
- date: {
42
- parse: (params: string[]) => Date;
43
- serialize: (arg: Date) => string;
44
- };
45
- dateArray: {
46
- parse: (params: string[]) => Date[];
47
- serialize: (args: Date[]) => string[];
48
- };
49
- zodSchema: <T>(schema: ZodInterface<T>) => Adapter<T>;
50
- };
51
- export {};