clear-react-router 1.4.0 → 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 CHANGED
@@ -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 a parser function, and it returns the parsed value and a setter.
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, parser } from 'clear-react-router';
308
+ import { useQueryParam, adapter } from 'clear-react-router';
309
309
 
310
310
  const ProductPage = () => {
311
311
  // String parameter
312
- const [brand, setBrand] = useQueryParam('brand', parser.string, 'nike');
312
+ const [brand, setBrand] = useQueryParam('brand', adapter.string, 'nike');
313
313
 
314
314
  // Number parameter
315
- const [page, setPage] = useQueryParam('page', parser.integer, 1);
315
+ const [page, setPage] = useQueryParam('page', adapter.integer, 1);
316
316
 
317
- // Boolean parameter
318
- const [isActive, setIsActive] = useQueryParam('active', parser.boolean, false);
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', parser.floatArray);
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
- **Signature:** `useQueryParam<T>(field: string, parser: (arg: string[]) => T, defaultValue?: T): [T, (arg: T) => void]`
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
- | `parser` | `(arg: string[]) => T` | Function that transforms the raw string array into the desired type |
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 Parsers
351
-
352
- | Parser | Input | Output | Description |
353
- |--------|-------|--------|-------------|
354
- | `parser.string` | `string[]` | `string` | First value or empty string |
355
- | `parser.stringArray` | `string[]` | `string[]` | All values as array |
356
- | `parser.integer` | `string[]` | `number` | First value parsed as integer (default: `0`) |
357
- | `parser.integerArray` | `string[]` | `number[]` | All values parsed as integers |
358
- | `parser.float` | `string[]` | `number` | First value parsed as float (default: `0`) |
359
- | `parser.floatArray` | `string[]` | `number[]` | All values parsed as floats |
360
- | `parser.boolean` | `string[]` | `boolean` | First value parsed as boolean (`'true'` → `true`) |
361
- | `parser.booleanArray` | `string[]` | `boolean[]` | All values parsed as booleans |
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, parser } from 'clear-react-router';
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
- parser.zodSchema(filterSchema),
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 Parsers
396
- You can write your own parser for any format:
400
+ ### Custom Adapters
401
+ You can write your own adapter for any format:
397
402
 
398
403
  ```
399
- // Custom parser for comma-separated values
400
- const csvParser = (params: string[]): string[] => {
401
- const value = params[0] || '';
402
- return value ? value.split(',').map(v => v.trim()) : [];
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', csvParser, []);
414
+ const [tags, setTags] = useQueryParam('tags', csvAdapter, []);
407
415
  // tags: string[]
408
416
  }
409
417
  ```
@@ -1 +1,2 @@
1
- export declare function useQueryParam<T>(field: string, parser: (arg: string[]) => T, defaultValue?: T): [T, (arg: T) => void];
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 { parser } from './utils/parser';
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, parser, defaultValue) {
678
+ function useQueryParam(field, adapter, defaultValue) {
679
679
  const { searchParams, setSearchParams } = useSearchParams();
680
680
  return [useMemo(() => {
681
- const result = parser(searchParams.getAll(field));
682
- if (result !== void 0 && result !== null && result !== "") return result;
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
- parser,
689
+ adapter,
688
690
  searchParams,
689
691
  defaultValue
690
692
  ]), useCallback((value) => {
691
- setSearchParams(field, (typeof value === "object" ? JSON.stringify : String)(value));
692
- }, [setSearchParams, field])];
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/parser.ts
710
- var parser = {
711
- string: (params) => params[0] || "",
712
- stringArray: (params) => params,
713
- integer: (params) => {
714
- const result = parseInt(params[0] || "", 10);
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
- integerArray: (params) => params.map((el) => {
718
- const result = parseInt(el, 10);
723
+ integer: { parse: (params) => {
724
+ const result = parseInt(params[0] || "");
719
725
  return isNaN(result) ? 0 : result;
720
- }),
721
- float: (params) => {
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
- floatArray: (params) => params.map((el) => {
726
- const result = parseFloat(el);
727
- return isNaN(result) ? 0 : result;
728
- }),
729
- boolean: (params) => params[0]?.toLowerCase() === "true",
730
- booleanArray: (params) => params.map((el) => el.toLowerCase() === "true"),
731
- zodSchema: (schema) => (params) => {
732
- let parsed;
733
- try {
734
- parsed = JSON.parse(params[0] ?? "{}");
735
- } catch {
736
- throw new Error("Invalid JSON");
737
- }
738
- const result = schema.safeParse(parsed);
739
- if (!result.success) throw new Error("Invalid schema");
740
- return result.data;
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, createRouter, parser, useBeforeUnload, useBlocker, useHistoricalTrail, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
777
+ export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useHistoricalTrail, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
@@ -55,3 +55,7 @@ export type LoaderState = Record<string, {
55
55
  loaderError: Error | null;
56
56
  beforeLoadError: Error | null;
57
57
  }>;
58
+ export type Adapter<T> = {
59
+ parse: (params: string[]) => T;
60
+ serialize?: (params: T) => string | string[];
61
+ };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -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 {};