clear-react-router 1.4.0 → 1.4.2
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 +45 -37
- package/dist/hooks/useQueryParam.d.ts +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +87 -43
- package/dist/types/global.d.ts +4 -0
- package/dist/utils/adapter.d.ts +56 -0
- package/package.json +1 -1
- package/dist/utils/parser.d.ts +0 -21
package/README.md
CHANGED
|
@@ -79,7 +79,7 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
|
|
|
79
79
|
</RouterProvider>
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
> **Note:** When `isAnimated` is enabled, `loaderFallback` is not shown. Instead, a small spinner appears (if `spinner={true}`).
|
|
82
|
+
> **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.
|
|
83
83
|
|
|
84
84
|
### `Link`
|
|
85
85
|
|
|
@@ -302,26 +302,23 @@ useBeforeUnload(text ? onSave : undefined);
|
|
|
302
302
|
|
|
303
303
|
### `useQueryParam()`
|
|
304
304
|
|
|
305
|
-
A flexible hook for working with typed query parameters. You provide
|
|
305
|
+
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.
|
|
306
306
|
|
|
307
307
|
```
|
|
308
|
-
import { useQueryParam,
|
|
308
|
+
import { useQueryParam, adapter } from 'clear-react-router';
|
|
309
309
|
|
|
310
310
|
const ProductPage = () => {
|
|
311
311
|
// String parameter
|
|
312
|
-
const [brand, setBrand] = useQueryParam('brand',
|
|
312
|
+
const [brand, setBrand] = useQueryParam('brand', adapter.string, 'nike');
|
|
313
313
|
|
|
314
314
|
// Number parameter
|
|
315
|
-
const [page, setPage] = useQueryParam('page',
|
|
315
|
+
const [page, setPage] = useQueryParam('page', adapter.integer, 1);
|
|
316
316
|
|
|
317
|
-
//
|
|
318
|
-
const [
|
|
319
|
-
|
|
320
|
-
// Array of strings
|
|
321
|
-
const [colors, setColors] = useQueryParam('colors', parser.stringArray);
|
|
317
|
+
// Date parameter
|
|
318
|
+
const [date, setDate] = useQueryParam('date', adapter.date, new Date());
|
|
322
319
|
|
|
323
320
|
// Array of numbers
|
|
324
|
-
const [prices, setPrices] = useQueryParam('prices',
|
|
321
|
+
const [prices, setPrices] = useQueryParam('prices', adapter.floatArray);
|
|
325
322
|
|
|
326
323
|
return (
|
|
327
324
|
<div>
|
|
@@ -332,12 +329,17 @@ const ProductPage = () => {
|
|
|
332
329
|
);
|
|
333
330
|
}
|
|
334
331
|
```
|
|
335
|
-
|
|
332
|
+
type Adapter<T> = {
|
|
333
|
+
parse: (params: string[]) => T;
|
|
334
|
+
serialize?: (params: T) => string | string[];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
**Signature:** `useQueryParam<T>(field: string, adapter: Adapter<T>, defaultValue?: T): [T, (arg: T | null) => void]`
|
|
336
338
|
|
|
337
339
|
| Argument | Type | Description |
|
|
338
340
|
|----------|------|-------------|
|
|
339
341
|
| `field` | `string` | The query parameter key (e.g., `'page'`, `'brand'`) |
|
|
340
|
-
| `
|
|
342
|
+
| `adapter` | `Adapter<T>` | Parser and optional serializer for params (serializer String is used in case of serializer not passed) |
|
|
341
343
|
| `defaultValue` | `T` (optional) | Default value returned when the parameter is missing or empty |
|
|
342
344
|
|
|
343
345
|
**Returns:**
|
|
@@ -345,27 +347,30 @@ const ProductPage = () => {
|
|
|
345
347
|
| Element | Type | Description |
|
|
346
348
|
|---------|------|-------------|
|
|
347
349
|
| `value` | `T` | The parsed value from the query parameter |
|
|
348
|
-
| `setValue` | `(arg: T) => void` | Function to update the query parameter |
|
|
349
|
-
|
|
350
|
-
### Built-in
|
|
351
|
-
|
|
352
|
-
|
|
|
353
|
-
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
350
|
+
| `setValue` | `(arg: T | null) => void` | Function to update the query parameter. Null is passed to remove the parameter. |
|
|
351
|
+
|
|
352
|
+
### Built-in Adapters
|
|
353
|
+
|
|
354
|
+
| Adapter | Input | Output | Description |
|
|
355
|
+
|---------|-------|--------|-------------|
|
|
356
|
+
| `adapter.string` | `string[]` | `string` | First value or empty string |
|
|
357
|
+
| `adapter.stringArray` | `string[]` | `string[]` | All values as array |
|
|
358
|
+
| `adapter.integer` | `string[]` | `number` | First value parsed as integer (default: `0`) |
|
|
359
|
+
| `adapter.integerArray` | `string[]` | `number[]` | All values parsed as integers |
|
|
360
|
+
| `adapter.float` | `string[]` | `number` | First value parsed as float (default: `0`) |
|
|
361
|
+
| `adapter.floatArray` | `string[]` | `number[]` | All values parsed as floats |
|
|
362
|
+
| `adapter.boolean` | `string[]` | `boolean` | First value parsed as boolean (`'true'` → `true`) |
|
|
363
|
+
| `adapter.booleanArray` | `string[]` | `boolean[]` | All values parsed as booleans |
|
|
364
|
+
| `adapter.date` | `string[]` | `Date` | First value parsed as Date from timestamp |
|
|
365
|
+
| `adapter.dateArray` | `string[]` | `Date[]` | All values parsed as Dates from timestamps |
|
|
366
|
+
| `adapter.zodSchema` | `string[]` | `T` | Validates JSON string against Zod schema |
|
|
362
367
|
|
|
363
368
|
### Using Zod Schemas
|
|
364
369
|
`useQueryParam` works seamlessly with Zod for complex validation:
|
|
365
370
|
|
|
366
371
|
```
|
|
367
372
|
import { z } from 'zod';
|
|
368
|
-
import { useQueryParam,
|
|
373
|
+
import { useQueryParam, adapter } from 'clear-react-router';
|
|
369
374
|
|
|
370
375
|
const filterSchema = z.object({
|
|
371
376
|
name: z.string(),
|
|
@@ -376,7 +381,7 @@ const filterSchema = z.object({
|
|
|
376
381
|
function ProductFilter() {
|
|
377
382
|
const [filter, setFilter] = useQueryParam(
|
|
378
383
|
'filter',
|
|
379
|
-
|
|
384
|
+
adapter.zodSchema(filterSchema),
|
|
380
385
|
{ name: '', age: 0 }
|
|
381
386
|
);
|
|
382
387
|
|
|
@@ -392,18 +397,21 @@ function ProductFilter() {
|
|
|
392
397
|
}
|
|
393
398
|
```
|
|
394
399
|
|
|
395
|
-
### Custom
|
|
396
|
-
You can write your own
|
|
400
|
+
### Custom Adapters
|
|
401
|
+
You can write your own adapter for any format:
|
|
397
402
|
|
|
398
403
|
```
|
|
399
|
-
// Custom
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
+
// Custom adapter for comma-separated values
|
|
405
|
+
const csvAdapter = {
|
|
406
|
+
parse: (params: string[]): string[] => {
|
|
407
|
+
const value = params[0] || '';
|
|
408
|
+
return value ? value.split(',').map(v => v.trim()) : [];
|
|
409
|
+
},
|
|
410
|
+
serialize: (value: string[]): string[] => value
|
|
411
|
+
}
|
|
404
412
|
|
|
405
413
|
const TagsFilter() {
|
|
406
|
-
const [tags, setTags] = useQueryParam('tags',
|
|
414
|
+
const [tags, setTags] = useQueryParam('tags', csvAdapter, []);
|
|
407
415
|
// tags: string[]
|
|
408
416
|
}
|
|
409
417
|
```
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import { Adapter } from '../types/global';
|
|
2
|
+
export declare function useQueryParam<T>(field: string, adapter: Adapter<T>, defaultValue?: T): [T, (arg: T | null) => void];
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,6 @@ export { useRouterContext } from './hooks/useRouterContext';
|
|
|
11
11
|
export { useQueryParam } from './hooks/useQueryParam';
|
|
12
12
|
export { useSearchParams } from './hooks/useSearchParams';
|
|
13
13
|
export { useHistoricalTrail } from './hooks/useHistoricalTrail';
|
|
14
|
-
export {
|
|
14
|
+
export { adapter } from './utils/adapter';
|
|
15
15
|
export { createRouter } from './utils/utils';
|
|
16
|
-
export type { RouteItem, BlockerState, Location } from './types/global';
|
|
16
|
+
export type { RouteItem, BlockerState, Location, Adapter } from './types/global';
|
package/dist/index.js
CHANGED
|
@@ -500,7 +500,6 @@ var renderElement = (Component) => {
|
|
|
500
500
|
};
|
|
501
501
|
//#endregion
|
|
502
502
|
//#region components/Router.tsx
|
|
503
|
-
var PAGE_NOT_FOUND = "error 404. Page not found";
|
|
504
503
|
var ALL_LOCATIONS = "*";
|
|
505
504
|
var Router = ({ spinner = true }) => {
|
|
506
505
|
const { location, isLoading } = useNavigationState();
|
|
@@ -510,15 +509,27 @@ var Router = ({ spinner = true }) => {
|
|
|
510
509
|
if (!location.pathname) return void 0;
|
|
511
510
|
return routeList.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, location.pathname));
|
|
512
511
|
}, [location.pathname, routeList]);
|
|
513
|
-
const
|
|
512
|
+
const pendingRoute = useMemo(() => {
|
|
513
|
+
if (location.pathname) return void 0;
|
|
514
|
+
return routeList.find((el) => comparePaths(el, window.location.pathname));
|
|
515
|
+
}, [location.pathname, routeList]);
|
|
516
|
+
const params = useMemo(() => {
|
|
517
|
+
return getParamsObject({
|
|
518
|
+
routeItem: routeItem || pendingRoute,
|
|
519
|
+
pathname: location.pathname || window.location.pathname
|
|
520
|
+
});
|
|
521
|
+
}, [
|
|
522
|
+
location.pathname,
|
|
514
523
|
routeItem,
|
|
515
|
-
|
|
516
|
-
|
|
524
|
+
pendingRoute
|
|
525
|
+
]);
|
|
517
526
|
const shouldErrorElementShown = useMemo(() => Boolean(loaderState[location.pathname]?.loaderError || loaderState[location.pathname]?.beforeLoadError), [loaderState, location.pathname]);
|
|
518
|
-
if (
|
|
519
|
-
if (!routeItem &&
|
|
520
|
-
|
|
521
|
-
|
|
527
|
+
if (!routeItem && !pendingRoute) return null;
|
|
528
|
+
if (!routeItem && pendingRoute) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
|
|
529
|
+
params,
|
|
530
|
+
children: renderElement(pendingRoute?.loaderFallback)
|
|
531
|
+
});
|
|
532
|
+
if (!isAnimated && !shouldErrorElementShown && isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
|
|
522
533
|
params,
|
|
523
534
|
children: renderElement(routeItem?.loaderFallback)
|
|
524
535
|
});
|
|
@@ -530,7 +541,7 @@ var Router = ({ spinner = true }) => {
|
|
|
530
541
|
style: { viewTransitionName: "page" },
|
|
531
542
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ViewProvider, {
|
|
532
543
|
params,
|
|
533
|
-
children: [renderElement(routeItem?.element) ||
|
|
544
|
+
children: [renderElement(routeItem?.element) || null, spinner && isAnimated && isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})]
|
|
534
545
|
})
|
|
535
546
|
});
|
|
536
547
|
};
|
|
@@ -675,21 +686,28 @@ var useSearchParams = () => {
|
|
|
675
686
|
};
|
|
676
687
|
//#endregion
|
|
677
688
|
//#region hooks/useQueryParam.ts
|
|
678
|
-
function useQueryParam(field,
|
|
689
|
+
function useQueryParam(field, adapter, defaultValue) {
|
|
679
690
|
const { searchParams, setSearchParams } = useSearchParams();
|
|
680
691
|
return [useMemo(() => {
|
|
681
|
-
const
|
|
682
|
-
|
|
692
|
+
const params = searchParams.getAll(field);
|
|
693
|
+
const result = adapter.parse(params);
|
|
694
|
+
const isValid = !(result instanceof Date) || result instanceof Date && !isNaN(result.getTime());
|
|
695
|
+
if (result !== void 0 && result !== null && result !== "" && isValid) return result;
|
|
683
696
|
if (defaultValue !== void 0) return defaultValue;
|
|
684
697
|
return result;
|
|
685
698
|
}, [
|
|
686
699
|
field,
|
|
687
|
-
|
|
700
|
+
adapter,
|
|
688
701
|
searchParams,
|
|
689
702
|
defaultValue
|
|
690
703
|
]), useCallback((value) => {
|
|
691
|
-
|
|
692
|
-
|
|
704
|
+
if (!value) return setSearchParams(field, []);
|
|
705
|
+
setSearchParams(field, (adapter.serialize || String)(value));
|
|
706
|
+
}, [
|
|
707
|
+
field,
|
|
708
|
+
adapter.serialize,
|
|
709
|
+
setSearchParams
|
|
710
|
+
])];
|
|
693
711
|
}
|
|
694
712
|
//#endregion
|
|
695
713
|
//#region hooks/useHistoricalTrail.ts
|
|
@@ -706,39 +724,65 @@ var useHistoricalTrail = () => {
|
|
|
706
724
|
return trail;
|
|
707
725
|
};
|
|
708
726
|
//#endregion
|
|
709
|
-
//#region utils/
|
|
710
|
-
var
|
|
711
|
-
string: (params) => params[0] || "",
|
|
712
|
-
stringArray:
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
return isNaN(result) ? 0 : result;
|
|
727
|
+
//#region utils/adapter.ts
|
|
728
|
+
var adapter = {
|
|
729
|
+
string: { parse: (params) => params[0] || "" },
|
|
730
|
+
stringArray: {
|
|
731
|
+
parse: (params) => params,
|
|
732
|
+
serialize: (value) => value
|
|
716
733
|
},
|
|
717
|
-
|
|
718
|
-
const result = parseInt(
|
|
734
|
+
integer: { parse: (params) => {
|
|
735
|
+
const result = parseInt(params[0] || "");
|
|
719
736
|
return isNaN(result) ? 0 : result;
|
|
720
|
-
}
|
|
721
|
-
|
|
737
|
+
} },
|
|
738
|
+
integerArray: {
|
|
739
|
+
parse: (params) => params.map((el) => {
|
|
740
|
+
const result = parseInt(el);
|
|
741
|
+
return isNaN(result) ? 0 : result;
|
|
742
|
+
}),
|
|
743
|
+
serialize: (value) => value.map(String)
|
|
744
|
+
},
|
|
745
|
+
float: { parse: (params) => {
|
|
722
746
|
const result = parseFloat(params[0] || "");
|
|
723
747
|
return isNaN(result) ? 0 : result;
|
|
748
|
+
} },
|
|
749
|
+
floatArray: {
|
|
750
|
+
parse: (params) => params.map((el) => {
|
|
751
|
+
const result = parseFloat(el);
|
|
752
|
+
return isNaN(result) ? 0 : result;
|
|
753
|
+
}),
|
|
754
|
+
serialize: (value) => value.map(String)
|
|
724
755
|
},
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
756
|
+
boolean: {
|
|
757
|
+
parse: (params) => params[0]?.toLowerCase() === "true",
|
|
758
|
+
serialize: String
|
|
759
|
+
},
|
|
760
|
+
booleanArray: {
|
|
761
|
+
parse: (params) => params.map((el) => el.toLowerCase() === "true"),
|
|
762
|
+
serialize: (value) => value.map(String)
|
|
763
|
+
},
|
|
764
|
+
date: {
|
|
765
|
+
parse: (params) => new Date(Number(params[0])),
|
|
766
|
+
serialize: (arg) => String(arg.getTime())
|
|
767
|
+
},
|
|
768
|
+
dateArray: {
|
|
769
|
+
parse: (params) => params.map((param) => new Date(Number(param))),
|
|
770
|
+
serialize: (args) => args.map((arg) => String(arg.getTime()))
|
|
771
|
+
},
|
|
772
|
+
zodSchema: {
|
|
773
|
+
parse: (schema) => (params) => {
|
|
774
|
+
let parsed;
|
|
775
|
+
try {
|
|
776
|
+
parsed = JSON.parse(params[0] ?? "{}");
|
|
777
|
+
} catch {
|
|
778
|
+
throw new Error("Invalid JSON");
|
|
779
|
+
}
|
|
780
|
+
const result = schema.safeParse(parsed);
|
|
781
|
+
if (!result.success) throw new Error("Invalid schema");
|
|
782
|
+
return result.data;
|
|
783
|
+
},
|
|
784
|
+
serialize: JSON.stringify
|
|
741
785
|
}
|
|
742
786
|
};
|
|
743
787
|
//#endregion
|
|
744
|
-
export { Link, Router, RouterProvider,
|
|
788
|
+
export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useHistoricalTrail, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
|
package/dist/types/global.d.ts
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
type ZodInterface<T> = {
|
|
2
|
+
safeParse(input: unknown): {
|
|
3
|
+
success: true;
|
|
4
|
+
data: T;
|
|
5
|
+
} | {
|
|
6
|
+
success: false;
|
|
7
|
+
error: unknown;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
export declare const adapter: {
|
|
11
|
+
string: {
|
|
12
|
+
parse: (params: string[]) => string;
|
|
13
|
+
};
|
|
14
|
+
stringArray: {
|
|
15
|
+
parse: (params: string[]) => string[];
|
|
16
|
+
serialize: (value: string[]) => string[];
|
|
17
|
+
};
|
|
18
|
+
integer: {
|
|
19
|
+
parse: (params: string[]) => number;
|
|
20
|
+
};
|
|
21
|
+
integerArray: {
|
|
22
|
+
parse: (params: string[]) => number[];
|
|
23
|
+
serialize: (value: number[]) => string[];
|
|
24
|
+
};
|
|
25
|
+
float: {
|
|
26
|
+
parse: (params: string[]) => number;
|
|
27
|
+
};
|
|
28
|
+
floatArray: {
|
|
29
|
+
parse: (params: string[]) => number[];
|
|
30
|
+
serialize: (value: number[]) => string[];
|
|
31
|
+
};
|
|
32
|
+
boolean: {
|
|
33
|
+
parse: (params: string[]) => boolean;
|
|
34
|
+
serialize: StringConstructor;
|
|
35
|
+
};
|
|
36
|
+
booleanArray: {
|
|
37
|
+
parse: (params: string[]) => boolean[];
|
|
38
|
+
serialize: (value: boolean[]) => string[];
|
|
39
|
+
};
|
|
40
|
+
date: {
|
|
41
|
+
parse: (params: string[]) => Date;
|
|
42
|
+
serialize: (arg: Date) => string;
|
|
43
|
+
};
|
|
44
|
+
dateArray: {
|
|
45
|
+
parse: (params: string[]) => Date[];
|
|
46
|
+
serialize: (args: Date[]) => string[];
|
|
47
|
+
};
|
|
48
|
+
zodSchema: {
|
|
49
|
+
parse: <T>(schema: ZodInterface<T>) => (params: string[]) => T;
|
|
50
|
+
serialize: {
|
|
51
|
+
(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string;
|
|
52
|
+
(value: any, replacer?: (number | string)[] | null, space?: string | number): string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
export {};
|
package/package.json
CHANGED
package/dist/utils/parser.d.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
type ZodInterface<T> = {
|
|
2
|
-
safeParse(input: unknown): {
|
|
3
|
-
success: true;
|
|
4
|
-
data: T;
|
|
5
|
-
} | {
|
|
6
|
-
success: false;
|
|
7
|
-
error: unknown;
|
|
8
|
-
};
|
|
9
|
-
};
|
|
10
|
-
export declare const parser: {
|
|
11
|
-
string: (params: string[]) => string;
|
|
12
|
-
stringArray: (params: string[]) => string[];
|
|
13
|
-
integer: (params: string[]) => number;
|
|
14
|
-
integerArray: (params: string[]) => number[];
|
|
15
|
-
float: (params: string[]) => number;
|
|
16
|
-
floatArray: (params: string[]) => number[];
|
|
17
|
-
boolean: (params: string[]) => boolean;
|
|
18
|
-
booleanArray: (params: string[]) => boolean[];
|
|
19
|
-
zodSchema: <T>(schema: ZodInterface<T>) => (params: string[]) => T;
|
|
20
|
-
};
|
|
21
|
-
export {};
|