clear-react-router 1.3.9 → 1.4.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 +57 -41
- package/dist/hooks/useQueryParam.d.ts +2 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +67 -34
- 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
|
@@ -224,13 +224,21 @@ const { pathname, search, state } = useLocation();
|
|
|
224
224
|
|
|
225
225
|
### `useLoaderState()`
|
|
226
226
|
|
|
227
|
-
Returns the cached data loaded by the current route's `loader`. Data is automatically cached and reused when navigating back to the same route.
|
|
227
|
+
Returns the cached data loaded by the current route's `loader`, along with any errors from `loader` or `beforeLoad`. Data is automatically cached and reused when navigating back to the same route.
|
|
228
|
+
|
|
229
|
+
**Returns:**
|
|
230
|
+
|
|
231
|
+
| Property | Type | Description |
|
|
232
|
+
|----------|------|-------------|
|
|
233
|
+
| `data` | `unknown` | The data returned from the route's `loader` |
|
|
234
|
+
| `loaderError` | `Error \| null` | Error from the `loader` (if any) |
|
|
235
|
+
| `beforeLoadError` | `Error \| null` | Error from the `beforeLoad` hook (if any) |
|
|
236
|
+
|
|
228
237
|
```
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
return <div>{user.name}</div>;
|
|
232
|
-
}
|
|
238
|
+
function UserProfile() {
|
|
239
|
+
const { data, loaderError, beforeLoadError } = useLoaderState();
|
|
233
240
|
```
|
|
241
|
+
|
|
234
242
|
### Caching behavior:
|
|
235
243
|
- 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).
|
|
236
244
|
- Use staleTime in route config to control how long cache is considered fresh:
|
|
@@ -294,26 +302,23 @@ useBeforeUnload(text ? onSave : undefined);
|
|
|
294
302
|
|
|
295
303
|
### `useQueryParam()`
|
|
296
304
|
|
|
297
|
-
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.
|
|
298
306
|
|
|
299
307
|
```
|
|
300
|
-
import { useQueryParam,
|
|
308
|
+
import { useQueryParam, adapter } from 'clear-react-router';
|
|
301
309
|
|
|
302
310
|
const ProductPage = () => {
|
|
303
311
|
// String parameter
|
|
304
|
-
const [brand, setBrand] = useQueryParam('brand',
|
|
312
|
+
const [brand, setBrand] = useQueryParam('brand', adapter.string, 'nike');
|
|
305
313
|
|
|
306
314
|
// Number parameter
|
|
307
|
-
const [page, setPage] = useQueryParam('page',
|
|
315
|
+
const [page, setPage] = useQueryParam('page', adapter.integer, 1);
|
|
308
316
|
|
|
309
|
-
//
|
|
310
|
-
const [
|
|
311
|
-
|
|
312
|
-
// Array of strings
|
|
313
|
-
const [colors, setColors] = useQueryParam('colors', parser.stringArray);
|
|
317
|
+
// Date parameter
|
|
318
|
+
const [date, setDate] = useQueryParam('date', adapter.date, new Date());
|
|
314
319
|
|
|
315
320
|
// Array of numbers
|
|
316
|
-
const [prices, setPrices] = useQueryParam('prices',
|
|
321
|
+
const [prices, setPrices] = useQueryParam('prices', adapter.floatArray);
|
|
317
322
|
|
|
318
323
|
return (
|
|
319
324
|
<div>
|
|
@@ -324,12 +329,17 @@ const ProductPage = () => {
|
|
|
324
329
|
);
|
|
325
330
|
}
|
|
326
331
|
```
|
|
327
|
-
|
|
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]`
|
|
328
338
|
|
|
329
339
|
| Argument | Type | Description |
|
|
330
340
|
|----------|------|-------------|
|
|
331
341
|
| `field` | `string` | The query parameter key (e.g., `'page'`, `'brand'`) |
|
|
332
|
-
| `
|
|
342
|
+
| `adapter` | `Adapter<T>` | Parser and optional serializer for params (serializer String is used in case of serializer not passed) |
|
|
333
343
|
| `defaultValue` | `T` (optional) | Default value returned when the parameter is missing or empty |
|
|
334
344
|
|
|
335
345
|
**Returns:**
|
|
@@ -337,27 +347,30 @@ const ProductPage = () => {
|
|
|
337
347
|
| Element | Type | Description |
|
|
338
348
|
|---------|------|-------------|
|
|
339
349
|
| `value` | `T` | The parsed value from the query parameter |
|
|
340
|
-
| `setValue` | `(arg: T) => void` | Function to update the query parameter |
|
|
341
|
-
|
|
342
|
-
### Built-in
|
|
343
|
-
|
|
344
|
-
|
|
|
345
|
-
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
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 |
|
|
354
367
|
|
|
355
368
|
### Using Zod Schemas
|
|
356
369
|
`useQueryParam` works seamlessly with Zod for complex validation:
|
|
357
370
|
|
|
358
371
|
```
|
|
359
372
|
import { z } from 'zod';
|
|
360
|
-
import { useQueryParam,
|
|
373
|
+
import { useQueryParam, adapter } from 'clear-react-router';
|
|
361
374
|
|
|
362
375
|
const filterSchema = z.object({
|
|
363
376
|
name: z.string(),
|
|
@@ -368,7 +381,7 @@ const filterSchema = z.object({
|
|
|
368
381
|
function ProductFilter() {
|
|
369
382
|
const [filter, setFilter] = useQueryParam(
|
|
370
383
|
'filter',
|
|
371
|
-
|
|
384
|
+
adapter.zodSchema(filterSchema),
|
|
372
385
|
{ name: '', age: 0 }
|
|
373
386
|
);
|
|
374
387
|
|
|
@@ -384,18 +397,21 @@ function ProductFilter() {
|
|
|
384
397
|
}
|
|
385
398
|
```
|
|
386
399
|
|
|
387
|
-
### Custom
|
|
388
|
-
You can write your own
|
|
400
|
+
### Custom Adapters
|
|
401
|
+
You can write your own adapter for any format:
|
|
389
402
|
|
|
390
403
|
```
|
|
391
|
-
// Custom
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
+
}
|
|
396
412
|
|
|
397
413
|
const TagsFilter() {
|
|
398
|
-
const [tags, setTags] = useQueryParam('tags',
|
|
414
|
+
const [tags, setTags] = useQueryParam('tags', csvAdapter, []);
|
|
399
415
|
// tags: string[]
|
|
400
416
|
}
|
|
401
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
|
@@ -675,21 +675,28 @@ var useSearchParams = () => {
|
|
|
675
675
|
};
|
|
676
676
|
//#endregion
|
|
677
677
|
//#region hooks/useQueryParam.ts
|
|
678
|
-
function useQueryParam(field,
|
|
678
|
+
function useQueryParam(field, adapter, defaultValue) {
|
|
679
679
|
const { searchParams, setSearchParams } = useSearchParams();
|
|
680
680
|
return [useMemo(() => {
|
|
681
|
-
const
|
|
682
|
-
|
|
681
|
+
const params = searchParams.getAll(field);
|
|
682
|
+
const result = adapter.parse(params);
|
|
683
|
+
const isValid = !(result instanceof Date) || result instanceof Date && !isNaN(result.getTime());
|
|
684
|
+
if (result !== void 0 && result !== null && result !== "" && isValid) return result;
|
|
683
685
|
if (defaultValue !== void 0) return defaultValue;
|
|
684
686
|
return result;
|
|
685
687
|
}, [
|
|
686
688
|
field,
|
|
687
|
-
|
|
689
|
+
adapter,
|
|
688
690
|
searchParams,
|
|
689
691
|
defaultValue
|
|
690
692
|
]), useCallback((value) => {
|
|
691
|
-
|
|
692
|
-
|
|
693
|
+
if (!value) return setSearchParams(field, []);
|
|
694
|
+
setSearchParams(field, (adapter.serialize || String)(value));
|
|
695
|
+
}, [
|
|
696
|
+
field,
|
|
697
|
+
adapter.serialize,
|
|
698
|
+
setSearchParams
|
|
699
|
+
])];
|
|
693
700
|
}
|
|
694
701
|
//#endregion
|
|
695
702
|
//#region hooks/useHistoricalTrail.ts
|
|
@@ -706,39 +713,65 @@ var useHistoricalTrail = () => {
|
|
|
706
713
|
return trail;
|
|
707
714
|
};
|
|
708
715
|
//#endregion
|
|
709
|
-
//#region utils/
|
|
710
|
-
var
|
|
711
|
-
string: (params) => params[0] || "",
|
|
712
|
-
stringArray:
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
return isNaN(result) ? 0 : result;
|
|
716
|
+
//#region utils/adapter.ts
|
|
717
|
+
var adapter = {
|
|
718
|
+
string: { parse: (params) => params[0] || "" },
|
|
719
|
+
stringArray: {
|
|
720
|
+
parse: (params) => params,
|
|
721
|
+
serialize: (value) => value
|
|
716
722
|
},
|
|
717
|
-
|
|
718
|
-
const result = parseInt(
|
|
723
|
+
integer: { parse: (params) => {
|
|
724
|
+
const result = parseInt(params[0] || "");
|
|
719
725
|
return isNaN(result) ? 0 : result;
|
|
720
|
-
}
|
|
721
|
-
|
|
726
|
+
} },
|
|
727
|
+
integerArray: {
|
|
728
|
+
parse: (params) => params.map((el) => {
|
|
729
|
+
const result = parseInt(el);
|
|
730
|
+
return isNaN(result) ? 0 : result;
|
|
731
|
+
}),
|
|
732
|
+
serialize: (value) => value.map(String)
|
|
733
|
+
},
|
|
734
|
+
float: { parse: (params) => {
|
|
722
735
|
const result = parseFloat(params[0] || "");
|
|
723
736
|
return isNaN(result) ? 0 : result;
|
|
737
|
+
} },
|
|
738
|
+
floatArray: {
|
|
739
|
+
parse: (params) => params.map((el) => {
|
|
740
|
+
const result = parseFloat(el);
|
|
741
|
+
return isNaN(result) ? 0 : result;
|
|
742
|
+
}),
|
|
743
|
+
serialize: (value) => value.map(String)
|
|
724
744
|
},
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
745
|
+
boolean: {
|
|
746
|
+
parse: (params) => params[0]?.toLowerCase() === "true",
|
|
747
|
+
serialize: String
|
|
748
|
+
},
|
|
749
|
+
booleanArray: {
|
|
750
|
+
parse: (params) => params.map((el) => el.toLowerCase() === "true"),
|
|
751
|
+
serialize: (value) => value.map(String)
|
|
752
|
+
},
|
|
753
|
+
date: {
|
|
754
|
+
parse: (params) => new Date(Number(params[0])),
|
|
755
|
+
serialize: (arg) => String(arg.getTime())
|
|
756
|
+
},
|
|
757
|
+
dateArray: {
|
|
758
|
+
parse: (params) => params.map((param) => new Date(Number(param))),
|
|
759
|
+
serialize: (args) => args.map((arg) => String(arg.getTime()))
|
|
760
|
+
},
|
|
761
|
+
zodSchema: {
|
|
762
|
+
parse: (schema) => (params) => {
|
|
763
|
+
let parsed;
|
|
764
|
+
try {
|
|
765
|
+
parsed = JSON.parse(params[0] ?? "{}");
|
|
766
|
+
} catch {
|
|
767
|
+
throw new Error("Invalid JSON");
|
|
768
|
+
}
|
|
769
|
+
const result = schema.safeParse(parsed);
|
|
770
|
+
if (!result.success) throw new Error("Invalid schema");
|
|
771
|
+
return result.data;
|
|
772
|
+
},
|
|
773
|
+
serialize: JSON.stringify
|
|
741
774
|
}
|
|
742
775
|
};
|
|
743
776
|
//#endregion
|
|
744
|
-
export { Link, Router, RouterProvider,
|
|
777
|
+
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 {};
|