paramsync 1.0.0 → 1.0.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 +72 -1286
- package/README.npm.md +153 -0
- package/package.json +13 -4
package/README.npm.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# paramsync
|
|
2
|
+
|
|
3
|
+
Type-safe, zero-overhead URL Search Parameters state manager for React and Next.js App Router applications.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/paramsync)
|
|
6
|
+
[](https://www.npmjs.com/package/paramsync)
|
|
7
|
+
[](https://bundlephobia.com/package/paramsync)
|
|
8
|
+
[](https://github.com/3b3zeem/paramsync)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What is paramsync?
|
|
13
|
+
|
|
14
|
+
In Next.js App Router applications, managing state through URL search parameters using standard `useSearchParams()` and `router.replace()` often introduces keystroke latency during live search, UI stutter, and cluttered URLs filled with empty or default values.
|
|
15
|
+
|
|
16
|
+
`paramsync` provides a seamless `[value, setValue]` state hook that binds React state directly to the URL:
|
|
17
|
+
- **0ms Input Latency:** Controlled inputs update local state instantaneously while background URL updates are debounced.
|
|
18
|
+
- **Clean URL Policy:** Default values, empty strings, and empty arrays are automatically purged from the query string.
|
|
19
|
+
- **Batch Transitions:** Multiple filter changes can be committed simultaneously in a single navigation transition via `useBatchQueryParams`.
|
|
20
|
+
- **Server Component Compatible:** Parsers have zero DOM dependencies and execute safely in React Server Components and Route Handlers.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install paramsync
|
|
28
|
+
# or
|
|
29
|
+
pnpm add paramsync
|
|
30
|
+
# or
|
|
31
|
+
yarn add paramsync
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Peer Dependencies
|
|
35
|
+
- `react` >= 18.0.0
|
|
36
|
+
- `next` >= 13.0.0 (App Router)
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick Example
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
'use client';
|
|
44
|
+
|
|
45
|
+
import { useQueryState, parseAsString, parseAsInteger } from 'paramsync';
|
|
46
|
+
|
|
47
|
+
export default function SearchFeed() {
|
|
48
|
+
// Live search with 300ms URL debounce (zero typing lag)
|
|
49
|
+
const [search, setSearch] = useQueryState('q', parseAsString.withDefault(''), {
|
|
50
|
+
debounceMs: 300,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Numeric page parameter
|
|
54
|
+
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div>
|
|
58
|
+
<input
|
|
59
|
+
type="text"
|
|
60
|
+
value={search}
|
|
61
|
+
onChange={(e) => {
|
|
62
|
+
setPage(1); // Reset page index on search
|
|
63
|
+
setSearch(e.target.value);
|
|
64
|
+
}}
|
|
65
|
+
placeholder="Filter results..."
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
<button disabled={page <= 1} onClick={() => setPage((p) => p - 1)}>
|
|
69
|
+
Prev
|
|
70
|
+
</button>
|
|
71
|
+
<span>Page {page}</span>
|
|
72
|
+
<button onClick={() => setPage((p) => p + 1)}>
|
|
73
|
+
Next
|
|
74
|
+
</button>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## API Summary
|
|
83
|
+
|
|
84
|
+
### Built-in Parsers
|
|
85
|
+
|
|
86
|
+
| Parser | Parse Signature | Clean URL Behavior | Example |
|
|
87
|
+
| :--- | :--- | :--- | :--- |
|
|
88
|
+
| `parseAsString` | `string \| null` | Omitted if `""` or default | `?q=laptop` |
|
|
89
|
+
| `parseAsInteger` | `number \| null` | Omitted if default | `?page=2` |
|
|
90
|
+
| `parseAsFloat` | `number \| null` | Omitted if default | `?rating=4.5` |
|
|
91
|
+
| `parseAsBoolean` | `boolean \| null` | Omitted if default | `?inStock=true` |
|
|
92
|
+
| `parseAsArray(parser, sep?)` | `T[]` | Omitted if `[]` or default | `?tags=shoes,sale` |
|
|
93
|
+
| `parseAsNumberRange(min, max, sep?)` | `{ min, max }` | Omitted if matches bounds | `?price=50-250` |
|
|
94
|
+
| `parseAsSort(field, order?, sep?)` | `{ field, order }` | Omitted if matches default | `?sort=price.asc` |
|
|
95
|
+
| `createParser(config)` | `T` | Custom serializer | User-defined |
|
|
96
|
+
|
|
97
|
+
> Calling `.withDefault(fallback)` on any parser narrows the returned state type from `T | null` to non-nullable `T`.
|
|
98
|
+
|
|
99
|
+
### Hooks
|
|
100
|
+
|
|
101
|
+
#### `useQueryState(key, parser, options?)`
|
|
102
|
+
Manages an individual URL parameter with optimistic local state.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const [value, setValue, reset, isPending] = useQueryState('q', parseAsString.withDefault(''), {
|
|
106
|
+
debounceMs: 300, // Debounce URL commit (0ms local state feedback)
|
|
107
|
+
history: 'replace', // 'replace' | 'push'
|
|
108
|
+
scroll: false, // Scroll to top on update
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
#### `useBatchQueryParams(options?)`
|
|
113
|
+
Executes multiple parameter changes in a single URL transition (for modals or drawers):
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
const { setBatchParams, resetAllParams, isPending } = useBatchQueryParams();
|
|
117
|
+
|
|
118
|
+
// Commit multiple filter updates in a single transition
|
|
119
|
+
setBatchParams({
|
|
120
|
+
q: 'shoes',
|
|
121
|
+
categories: { value: ['running'], parser: parseAsArray(parseAsString) },
|
|
122
|
+
price: { value: { min: 50, max: 200 }, parser: parseAsNumberRange(0, 1000) },
|
|
123
|
+
page: null, // Deletes 'page' from URL (resets to page 1)
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Wipe all non-essential parameters at once
|
|
127
|
+
resetAllParams(['tab', 'utm_source']);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Clean URL Policy
|
|
133
|
+
|
|
134
|
+
`paramsync` automatically deletes a parameter from the query string when:
|
|
135
|
+
1. Its value equals `defaultValue`.
|
|
136
|
+
2. Its value is an empty string `""`.
|
|
137
|
+
3. Its value is an empty array `[]`.
|
|
138
|
+
4. Its value is `null` or `undefined`.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Documentation & Repository
|
|
143
|
+
|
|
144
|
+
For complete architectural guides, Server Components (RSC) data fetching patterns, TanStack Query integration, dynamic facet filtering, and recipes:
|
|
145
|
+
|
|
146
|
+
- **Repository & Full Docs:** [https://github.com/3b3zeem/paramsync](https://github.com/3b3zeem/paramsync)
|
|
147
|
+
- **Issues & Feedback:** [https://github.com/3b3zeem/paramsync/issues](https://github.com/3b3zeem/paramsync/issues)
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT © [Ahmed Mostafa](https://github.com/3b3zeem/paramsync)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paramsync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Zero-overhead, type-safe URL Search Params state manager for React & Next.js App Router applications.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -26,8 +26,17 @@
|
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
29
|
+
"prepublishOnly": "npm run typecheck && npm run test && npm run build",
|
|
30
|
+
"prepack": "node scripts/prepare-npm-readme.js",
|
|
31
|
+
"postpack": "node scripts/restore-github-readme.js"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/3b3zeem/paramsync.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/3b3zeem/paramsync#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/3b3zeem/paramsync/issues"
|
|
31
40
|
},
|
|
32
41
|
"keywords": [
|
|
33
42
|
"react",
|
|
@@ -41,7 +50,7 @@
|
|
|
41
50
|
"nuqs",
|
|
42
51
|
"paramsync"
|
|
43
52
|
],
|
|
44
|
-
"author": "",
|
|
53
|
+
"author": "Ahmed Mostafa <ahmedmostafaabdel3azem@gmail.com>",
|
|
45
54
|
"license": "MIT",
|
|
46
55
|
"type": "module",
|
|
47
56
|
"peerDependencies": {
|