state-in-url 3.0.6 → 3.1.0
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.CN.md +1 -1
- package/README.KO.md +1 -1
- package/README.md +56 -34
- package/dist/next/useUrlState/useUrlState.d.ts +22 -10
- package/dist/next/useUrlState/useUrlState.mjs +1 -1
- package/dist/react-router/useUrlState/useUrlState.d.ts +23 -11
- package/dist/react-router/useUrlState/useUrlState.mjs +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/package.json +4 -4
package/README.CN.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|

|
|
24
24
|
|
|
25
|
-
<a href="https://state-in-url
|
|
25
|
+
<a href="https://state-in-url.dev/" target="_blank">演示</a> |
|
|
26
26
|
<a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
|
|
27
27
|
|
|
28
28
|
<hr />
|
package/README.KO.md
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|

|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
<a href="https://state-in-url
|
|
29
|
+
<a href="https://state-in-url.dev" target="_blank">데모</a> |
|
|
30
30
|
<a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
|
|
31
31
|
|
|
32
32
|
|
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
|
|
|
31
31
|
|
|
32
32
|

|
|
33
33
|
|
|
34
|
-
<a href="https://state-in-url
|
|
34
|
+
<a href="https://state-in-url.dev" target="_blank">DEMO</a> |
|
|
35
35
|
<a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a>
|
|
36
36
|
|
|
37
37
|
<hr />
|
|
@@ -119,6 +119,8 @@ Possibly need to set `"module": "ES2022"`, or `"module": "ESNext"`
|
|
|
119
119
|
|
|
120
120
|
[Docs](packages/urlstate/next/useUrlState#api)
|
|
121
121
|
|
|
122
|
+
[React-Router example](#useurlstate-hook-for-react-router)
|
|
123
|
+
|
|
122
124
|
### Usage examples
|
|
123
125
|
|
|
124
126
|
#### Basic
|
|
@@ -142,31 +144,28 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
142
144
|
import { userState } from './userState';
|
|
143
145
|
|
|
144
146
|
function MyComponent() {
|
|
145
|
-
// can pass `replace` arg, it's control will `
|
|
146
|
-
//
|
|
147
|
-
const {
|
|
147
|
+
// can pass `replace` arg, it's control will `setUrl` will use `rounter.push` or `router.replace`, default replace=true
|
|
148
|
+
// can pass `searchParams` from server components
|
|
149
|
+
const { urlState, setUrl } = useUrlState({ defaultState: userState });
|
|
148
150
|
|
|
149
151
|
// won't let you to accidently mutate state directly, requires TS
|
|
150
|
-
//
|
|
152
|
+
// urlState.name = 'John' // <- error
|
|
151
153
|
|
|
152
154
|
return (
|
|
153
155
|
<div>
|
|
154
|
-
<input value={
|
|
155
|
-
onChange={(ev) => {
|
|
156
|
-
onBlur={() => updateUrl()}
|
|
156
|
+
<input value={urlState.name}
|
|
157
|
+
onChange={(ev) => { setUrl({ name: ev.target.value }) }}
|
|
157
158
|
/>
|
|
158
|
-
<input value={
|
|
159
|
-
onChange={(ev) => {
|
|
160
|
-
onBlur={() => updateUrl()}
|
|
159
|
+
<input value={urlState.age}
|
|
160
|
+
onChange={(ev) => { setUrl({ age: +ev.target.value }) }}
|
|
161
161
|
/>
|
|
162
162
|
|
|
163
163
|
// same api as React.useState
|
|
164
|
-
<input value={
|
|
165
|
-
onChange={(ev) => {
|
|
166
|
-
onBlur={() => updateUrl()}
|
|
164
|
+
<input value={urlState.name}
|
|
165
|
+
onChange={(ev) => { setUrl(curr => ({ ...curr, name: ev.target.value })) }}
|
|
167
166
|
/>
|
|
168
167
|
|
|
169
|
-
<button onClick={() =>
|
|
168
|
+
<button onClick={() => setUrl(userState)}>
|
|
170
169
|
Reset
|
|
171
170
|
</button>
|
|
172
171
|
|
|
@@ -177,6 +176,9 @@ function MyComponent() {
|
|
|
177
176
|
|
|
178
177
|
#### With complex state shape
|
|
179
178
|
|
|
179
|
+
<details>
|
|
180
|
+
<Summary>Example</Summary>
|
|
181
|
+
|
|
180
182
|
```typescript
|
|
181
183
|
export const form: Form = {
|
|
182
184
|
name: '',
|
|
@@ -191,7 +193,6 @@ type Form = {
|
|
|
191
193
|
'agree to terms': boolean;
|
|
192
194
|
tags: { id: string; value: { text: string; time: Date } }[];
|
|
193
195
|
};
|
|
194
|
-
|
|
195
196
|
```
|
|
196
197
|
|
|
197
198
|
```typescript
|
|
@@ -201,19 +202,19 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
201
202
|
import { form } from './form';
|
|
202
203
|
|
|
203
204
|
function TagsComponent() {
|
|
204
|
-
// `
|
|
205
|
-
const {
|
|
205
|
+
// `urlState` will infer from Form type!
|
|
206
|
+
const { urlState, setUrl } = useUrlState({ defaultState: form });
|
|
206
207
|
|
|
207
208
|
const onChangeTags = React.useCallback(
|
|
208
209
|
(tag: (typeof tags)[number]) => {
|
|
209
|
-
|
|
210
|
+
setUrl((curr) => ({
|
|
210
211
|
...curr,
|
|
211
212
|
tags: curr.tags.find((t) => t.id === tag.id)
|
|
212
213
|
? curr.tags.filter((t) => t.id !== tag.id)
|
|
213
214
|
: curr.tags.concat(tag),
|
|
214
215
|
}));
|
|
215
216
|
},
|
|
216
|
-
[
|
|
217
|
+
[setUrl],
|
|
217
218
|
);
|
|
218
219
|
|
|
219
220
|
return (
|
|
@@ -222,7 +223,7 @@ function TagsComponent() {
|
|
|
222
223
|
<div className="flex flex-wrap gap-2">
|
|
223
224
|
{tags.map((tag) => (
|
|
224
225
|
<Tag
|
|
225
|
-
active={!!
|
|
226
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
226
227
|
text={tag.value.text}
|
|
227
228
|
onClick={() => onChangeTags(tag)}
|
|
228
229
|
key={tag.id}
|
|
@@ -251,22 +252,27 @@ const tags = [
|
|
|
251
252
|
```
|
|
252
253
|
|
|
253
254
|
[Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
|
|
255
|
+
</details>
|
|
254
256
|
|
|
255
|
-
####
|
|
257
|
+
#### Update state only and sync to URL manually
|
|
258
|
+
|
|
259
|
+
<details>
|
|
260
|
+
<Summary>Example</Summary>
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
256
263
|
|
|
257
|
-
```typescript
|
|
258
264
|
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
|
|
259
265
|
React.useEffect(() => {
|
|
260
266
|
clearTimeout(timer.current);
|
|
261
267
|
timer.current = setTimeout(() => {
|
|
262
268
|
// will compare state by content not by reference and fire update only for new values
|
|
263
|
-
|
|
269
|
+
setUrl(urlState);
|
|
264
270
|
}, 500);
|
|
265
271
|
|
|
266
272
|
return () => {
|
|
267
273
|
clearTimeout(timer.current);
|
|
268
274
|
};
|
|
269
|
-
}, [
|
|
275
|
+
}, [urlState, setUrl]);
|
|
270
276
|
```
|
|
271
277
|
|
|
272
278
|
Syncing state `onBlur` will be more aligned with real world usage.
|
|
@@ -275,8 +281,13 @@ Syncing state `onBlur` will be more aligned with real world usage.
|
|
|
275
281
|
<input onBlur={() => updateUrl()} .../>
|
|
276
282
|
```
|
|
277
283
|
|
|
284
|
+
</details>
|
|
285
|
+
|
|
278
286
|
#### With server side rendering
|
|
279
287
|
|
|
288
|
+
<details>
|
|
289
|
+
<Summary>Example</Summary>
|
|
290
|
+
|
|
280
291
|
```typescript
|
|
281
292
|
export default async function Home({ searchParams }: { searchParams: object }) {
|
|
282
293
|
return (
|
|
@@ -291,13 +302,17 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
291
302
|
import { form } from './form';
|
|
292
303
|
|
|
293
304
|
const Form = ({ searchParams }: { searchParams: object }) => {
|
|
294
|
-
const {
|
|
305
|
+
const { urlState, setState, setUrl } = useUrlState({ defaultState: form, searchParams });
|
|
295
306
|
}
|
|
296
307
|
```
|
|
297
308
|
|
|
309
|
+
</details>
|
|
310
|
+
|
|
298
311
|
#### Using hook in `layout` component
|
|
299
312
|
|
|
300
|
-
|
|
313
|
+
<details>
|
|
314
|
+
<Summary>Example</Summary>
|
|
315
|
+
That a tricky part, since nextjs with app router doesn't allow to access searchParams from server side. There is workaround with using middleware, but it isn't pretty and can stop working after nextjs update.
|
|
301
316
|
|
|
302
317
|
```typescript
|
|
303
318
|
// add to appropriate `layout.tsc`
|
|
@@ -342,8 +357,13 @@ export default async function Layout({
|
|
|
342
357
|
|
|
343
358
|
```
|
|
344
359
|
|
|
360
|
+
</details>
|
|
361
|
+
|
|
345
362
|
#### With arbitrary state shape (not recommended)
|
|
346
363
|
|
|
364
|
+
<details>
|
|
365
|
+
<Summary>Example</Summary>
|
|
366
|
+
|
|
347
367
|
```typescript
|
|
348
368
|
'use client'
|
|
349
369
|
import { useUrlState } from 'state-in-url/next';
|
|
@@ -351,10 +371,12 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
351
371
|
const someObj = {};
|
|
352
372
|
|
|
353
373
|
function SettingsComponent() {
|
|
354
|
-
const {
|
|
374
|
+
const { urlState, setUrl, setState } = useUrlState<object>(someObj);
|
|
355
375
|
}
|
|
356
376
|
```
|
|
357
377
|
|
|
378
|
+
</details>
|
|
379
|
+
|
|
358
380
|
## useUrlState hook for React-Router
|
|
359
381
|
|
|
360
382
|
API is same as for Next.js version, except can pass options from [NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) type.
|
|
@@ -386,18 +408,18 @@ import { useUrlState } from 'state-in-url/react-router';
|
|
|
386
408
|
import { form } from './form';
|
|
387
409
|
|
|
388
410
|
function TagsComponent() {
|
|
389
|
-
const {
|
|
411
|
+
const { urlState, setUrl } = useUrlState({ defaultState: form });
|
|
390
412
|
|
|
391
413
|
const onChangeTags = React.useCallback(
|
|
392
414
|
(tag: (typeof tags)[number]) => {
|
|
393
|
-
|
|
415
|
+
setUrl((curr) => ({
|
|
394
416
|
...curr,
|
|
395
417
|
tags: curr.tags.find((t) => t.id === tag.id)
|
|
396
418
|
? curr.tags.filter((t) => t.id !== tag.id)
|
|
397
419
|
: curr.tags.concat(tag),
|
|
398
420
|
}));
|
|
399
421
|
},
|
|
400
|
-
[
|
|
422
|
+
[setUrl],
|
|
401
423
|
);
|
|
402
424
|
|
|
403
425
|
return (
|
|
@@ -406,7 +428,7 @@ function TagsComponent() {
|
|
|
406
428
|
<div className="flex flex-wrap gap-2">
|
|
407
429
|
{tags.map((tag) => (
|
|
408
430
|
<Tag
|
|
409
|
-
active={!!
|
|
431
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
410
432
|
text={tag.value.text}
|
|
411
433
|
onClick={() => onChangeTags(tag)}
|
|
412
434
|
key={tag.id}
|
|
@@ -478,8 +500,6 @@ function SettingsComponent() {
|
|
|
478
500
|
- Define your state shape as a constant
|
|
479
501
|
- Use TypeScript for enhanced type safety and autocomplete
|
|
480
502
|
- Avoid storing sensitive information in URL parameters (SSN, API keys etc)
|
|
481
|
-
- Use `updateState` for frequent updates and `updateUrl` to sync changes to url
|
|
482
|
-
- Use `Suspence` to wrap client components in Next.js
|
|
483
503
|
- Use this [extension](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) for readable TS errors
|
|
484
504
|
|
|
485
505
|
## Gothas
|
|
@@ -513,6 +533,8 @@ This project is licensed under the [MIT license](LICENSE).
|
|
|
513
533
|
|
|
514
534
|
## Inspiration
|
|
515
535
|
|
|
536
|
+
[NUQS](https://github.com/47ng/nuqs)
|
|
537
|
+
|
|
516
538
|
[Using URL to store state in Vue](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)
|
|
517
539
|
|
|
518
540
|
[Storing state in the URL](https://antonz.org/storing-state/)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useRouter } from "next/navigation";
|
|
2
2
|
import { type DeepReadonly, type JSONCompatible } from "../../utils";
|
|
3
3
|
/**
|
|
4
|
-
* NextJS hook. Returns `
|
|
4
|
+
* NextJS hook. Returns `urlState`, `setState`, and `setUrl` functions
|
|
5
5
|
*
|
|
6
6
|
* @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
|
|
7
7
|
* @param {?SearchParams<T>} [searchParams] searchParams from Next server component
|
|
@@ -9,15 +9,15 @@ import { type DeepReadonly, type JSONCompatible } from "../../utils";
|
|
|
9
9
|
* * Example:
|
|
10
10
|
* ```ts
|
|
11
11
|
* export const form = { name: '', age: 0 };
|
|
12
|
-
* const {
|
|
12
|
+
* const { urlState, setState, setUrl } = useUrlState({ defaultState: form });
|
|
13
13
|
* // for nextjs seerver components
|
|
14
|
-
* // const {
|
|
14
|
+
* // const { urlState, setState, setUrl } = useUrlState({ defaultState: form, searchParams });
|
|
15
15
|
*
|
|
16
|
-
*
|
|
16
|
+
* setState({ name: 'test' });
|
|
17
17
|
* // by default it's uses router.push with scroll: false
|
|
18
|
-
*
|
|
18
|
+
* setUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
19
19
|
* // similar to React.useState
|
|
20
|
-
*
|
|
20
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
|
|
21
21
|
* ```
|
|
22
22
|
*
|
|
23
23
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#api}
|
|
@@ -31,25 +31,37 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState, se
|
|
|
31
31
|
/**
|
|
32
32
|
* * Example:
|
|
33
33
|
* ```ts
|
|
34
|
-
*
|
|
34
|
+
* setState({ name: 'test' });
|
|
35
35
|
* // or
|
|
36
|
-
*
|
|
36
|
+
* setState(curr => ({ ...curr, name: 'test' }) );
|
|
37
37
|
* ```
|
|
38
38
|
*
|
|
39
39
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updatestate}
|
|
40
40
|
*/
|
|
41
|
+
setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
42
|
+
/**
|
|
43
|
+
* @deprecated use `setState`
|
|
44
|
+
*/
|
|
41
45
|
updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
42
46
|
/**
|
|
43
47
|
* * Example:
|
|
44
48
|
* ```ts
|
|
45
|
-
*
|
|
49
|
+
* setUrl({ name: 'test' });
|
|
46
50
|
* // or
|
|
47
|
-
*
|
|
51
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
|
|
48
52
|
* ```
|
|
49
53
|
*
|
|
50
54
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updateurl}
|
|
51
55
|
*/
|
|
56
|
+
setUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
|
|
57
|
+
/**
|
|
58
|
+
* @deprecated use `setUrl`
|
|
59
|
+
*/
|
|
52
60
|
updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
|
|
61
|
+
urlState: DeepReadonly<DeepReadonly<T>>;
|
|
62
|
+
/**
|
|
63
|
+
* @deprecated use `urlState`
|
|
64
|
+
*/
|
|
53
65
|
state: DeepReadonly<DeepReadonly<T>>;
|
|
54
66
|
getState: () => T;
|
|
55
67
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRouter as t,useSearchParams as e}from"next/navigation";import a from"react";import{parseSPObj as r}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{isSSR as o,filterUnknownParams as m,filterUnknownParamsClient as
|
|
1
|
+
import{useRouter as t,useSearchParams as e}from"next/navigation";import a from"react";import{parseSPObj as r}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{isSSR as o,filterUnknownParams as m,filterUnknownParamsClient as l}from"../../utils.mjs";function p({defaultState:p,searchParams:c,...i}){const n=t(),{state:f,updateState:S,updateUrl:d,getState:j}=s(p,n,(({parse:t})=>o()?r(m(p,c),p):t(l(p)))),U=a.useCallback(((t,e)=>{const a={...u,...i,...e};d(t,a)}),[d,i]),b=e();return a.useEffect((()=>{S(m(p,r(Object.fromEntries([...b.entries()]),p)))}),[b]),{setState:S,updateState:S,setUrl:U,updateUrl:U,urlState:f,state:f,getState:j}}const u={replace:!0,scroll:!1};export{p as useUrlState};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { type NavigateOptions } from "react-router-dom";
|
|
2
2
|
import { type DeepReadonly, type JSONCompatible } from "../../utils";
|
|
3
3
|
/**
|
|
4
|
-
* React-router hook. Returns `
|
|
4
|
+
* React-router hook. Returns `urlState`, `setState`, and `setUrl` functions
|
|
5
5
|
*
|
|
6
6
|
* @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
|
|
7
7
|
* @param {NavigateOptions} [NavigateOptions] See type from `react-router-dom`
|
|
8
8
|
* * Example:
|
|
9
9
|
* ```ts
|
|
10
10
|
* export const form = { name: '', age: 0 };
|
|
11
|
-
* const {
|
|
11
|
+
* const { urlState, setState, setUrl } = useUrlState({ defaultState: form, replace: false, preventScrollReset: false });
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* setState({ name: 'test' });
|
|
14
|
+
* setUrl({ name: 'test' }, { replace: true });
|
|
15
15
|
* // similar to React.useState
|
|
16
|
-
*
|
|
16
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
|
|
17
17
|
* ```
|
|
18
18
|
*
|
|
19
19
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#api}
|
|
@@ -24,29 +24,41 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState, ..
|
|
|
24
24
|
/**
|
|
25
25
|
* * Example:
|
|
26
26
|
* ```ts
|
|
27
|
-
*
|
|
27
|
+
* setUrlState({ name: 'test' });
|
|
28
28
|
* // or
|
|
29
|
-
*
|
|
29
|
+
* setUrlState(curr => ({ ...curr, name: 'test' }) );
|
|
30
30
|
* // can pass optional React-Router `NavigateOptions`
|
|
31
|
-
*
|
|
31
|
+
* setUrlState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
|
|
32
32
|
* ```
|
|
33
33
|
*
|
|
34
34
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
|
|
35
35
|
*/
|
|
36
|
+
setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
37
|
+
/**
|
|
38
|
+
* @deprecated use `setState`
|
|
39
|
+
*/
|
|
36
40
|
updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
37
41
|
/**
|
|
38
42
|
* * Example:
|
|
39
43
|
* ```ts
|
|
40
|
-
*
|
|
44
|
+
* setUrl({ name: 'test' });
|
|
41
45
|
* // or
|
|
42
|
-
*
|
|
46
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
|
|
43
47
|
* // can pass optional React-Router `NavigateOptions`
|
|
44
|
-
*
|
|
48
|
+
* setState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
|
|
45
49
|
* ```
|
|
46
50
|
*
|
|
47
51
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
|
|
48
52
|
*/
|
|
53
|
+
setUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
|
|
54
|
+
/**
|
|
55
|
+
* @deprecated use `setUrl`
|
|
56
|
+
*/
|
|
49
57
|
updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
|
|
58
|
+
urlState: DeepReadonly<DeepReadonly<T>>;
|
|
59
|
+
/**
|
|
60
|
+
* @deprecated use `urlState`
|
|
61
|
+
*/
|
|
50
62
|
state: DeepReadonly<DeepReadonly<T>>;
|
|
51
63
|
getState: () => T;
|
|
52
64
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{useNavigate as e,useSearchParams as r}from"react-router-dom";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{filterUnknownParamsClient as o,assignValue as m,filterUnknownParams as p}from"../../utils.mjs";function u({defaultState:u,...
|
|
1
|
+
import t from"react";import{useNavigate as e,useSearchParams as r}from"react-router-dom";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{filterUnknownParamsClient as o,assignValue as m,filterUnknownParams as p}from"../../utils.mjs";function u({defaultState:u,...c}){const S=e(),f=t.useMemo((()=>({replace:(t,e)=>S(t,{...l,...c,...e}),push:(t,e)=>S(t,{...l,...c,...e})})),[S,c]),{state:i,updateState:n,updateUrl:d,getState:j}=s(u,f,(({parse:t})=>t(o(u)))),U=t.useCallback(((t,e)=>{const r={...l,...c,...e};d(t,r)}),[c]),[b]=r();return t.useEffect((()=>{n(m(u,i,p(u,a(Object.fromEntries([...b.entries()]),u))))}),[b]),{setState:n,updateState:n,setUrl:U,updateUrl:U,urlState:i,state:i,getState:j}}const l={replace:!0,preventScrollReset:!0};export{u as useUrlState};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{useInsertionEffect as e}from"../useInsertionEffect.mjs";import{useSharedState as
|
|
1
|
+
import t from"react";import{useInsertionEffect as e}from"../useInsertionEffect.mjs";import{useSharedState as n}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as o}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as r}from"../utils.mjs";function s(s,c,i){const{parse:u,stringify:f}=o(s),{state:h,getState:l,setState:m}=n(s,(()=>i?.({parse:u})||s));e((()=>{const t=()=>{const t=u(r(s));m(t)};return window.addEventListener(a,t),()=>{window.removeEventListener(a,t)}}),[m]);const p=t.useRef([]),d=t.useCallback(((t,e)=>{const n="function"==typeof t?t(l()):t?{...l(),...t}:l(),o=f(n,function(t){const e=Object.keys(t),n=window.location.search,o=new URLSearchParams(n),r=new URLSearchParams;return o.forEach(((t,n)=>!e.includes(n)&&r.set(n,t))),r}(s)),r=`${window.location.pathname}${o.length?"?":""}${o}${window.location.hash}`;if(r===`${window.location.pathname}${window.location.search}${window.location.hash}`)return;let a;m(n);const{replace:i,...u}=e||{};p.current.push([i?"replace":"push",r,u]),1===p.current.length&&queueMicrotask((()=>{for(;p.current.length;){const t=p.current.shift();if(!t)break;t?.[1]!==a?.[1]&&(a=t)}if(!a)return;const[t,e,n]=a;a=void 0,c[t](e,n)}))}),[c,f,l]);return{updateState:m,updateUrl:d,state:h,getState:l}}const a="popstate";export{s as useUrlStateBase};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Easily share complex state objects between unrelated React components, preserve types and structure, with TS validation. Deep links and url state synchronization wthout any hasssle or boilerplate.",
|
|
5
|
-
"homepage": "https://state-in-url
|
|
5
|
+
"homepage": "https://state-in-url.dev",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"front-end",
|
|
8
8
|
"state-management",
|
|
@@ -350,7 +350,7 @@
|
|
|
350
350
|
"@babel/preset-typescript": "^7.24.7",
|
|
351
351
|
"@commitlint/cli": "^19.0.3",
|
|
352
352
|
"@commitlint/config-conventional": "^19.0.3",
|
|
353
|
-
"@playwright/test": "^1.
|
|
353
|
+
"@playwright/test": "^1.48.2",
|
|
354
354
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
|
355
355
|
"@rollup/plugin-terser": "^0.4.4",
|
|
356
356
|
"@rollup/plugin-typescript": "^12.1.0",
|
|
@@ -386,7 +386,7 @@
|
|
|
386
386
|
"jest-environment-jsdom": "^29.7.0",
|
|
387
387
|
"lint-staged": "^15.2.9",
|
|
388
388
|
"only-allow": "^1.2.1",
|
|
389
|
-
"playwright": "^1.
|
|
389
|
+
"playwright": "^1.48.2",
|
|
390
390
|
"prettier": "^3.2.5",
|
|
391
391
|
"prettier-plugin-tailwindcss": "^0.6.8",
|
|
392
392
|
"release-it": "^17.6.0",
|