state-in-url 3.0.7 → 4.0.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.md +38 -38
- package/dist/next/useUrlState/useUrlState.d.ts +25 -11
- package/dist/next/useUrlState/useUrlState.mjs +1 -1
- package/dist/react-router/useUrlState/useUrlState.d.ts +26 -12
- package/dist/react-router/useUrlState/useUrlState.mjs +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.d.ts +1 -5
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/dist/utils.d.ts +5 -0
- package/dist/utils.mjs +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -144,31 +144,30 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
144
144
|
import { userState } from './userState';
|
|
145
145
|
|
|
146
146
|
function MyComponent() {
|
|
147
|
-
// can pass `replace` arg, it's control will `
|
|
148
|
-
//
|
|
149
|
-
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, setUrlState } = useUrlState({ defaultState: userState });
|
|
150
150
|
|
|
151
151
|
// won't let you to accidently mutate state directly, requires TS
|
|
152
|
-
//
|
|
152
|
+
// urlState.name = 'John' // <- error
|
|
153
153
|
|
|
154
154
|
return (
|
|
155
155
|
<div>
|
|
156
|
-
<input value={
|
|
157
|
-
onChange={(ev) => {
|
|
158
|
-
onBlur={() => updateUrl()}
|
|
156
|
+
<input value={urlState.name}
|
|
157
|
+
onChange={(ev) => { setUrl({ name: ev.target.value }) }}
|
|
159
158
|
/>
|
|
160
|
-
<input value={
|
|
161
|
-
onChange={(ev) => {
|
|
162
|
-
onBlur={() => updateUrl()}
|
|
159
|
+
<input value={urlState.age}
|
|
160
|
+
onChange={(ev) => { setUrl({ age: +ev.target.value }) }}
|
|
163
161
|
/>
|
|
164
162
|
|
|
165
163
|
// same api as React.useState
|
|
166
|
-
<input value={
|
|
167
|
-
onChange={(ev) => {
|
|
168
|
-
|
|
164
|
+
<input value={urlState.name}
|
|
165
|
+
onChange={(ev) => { setUrlState(curr => ({ ...curr, name: ev.target.value })) }}
|
|
166
|
+
// Can update state immediately but sync change to url as needed
|
|
167
|
+
onBlur={() => setUrl()}
|
|
169
168
|
/>
|
|
170
169
|
|
|
171
|
-
<button onClick={() =>
|
|
170
|
+
<button onClick={() => setUrl(userState)}>
|
|
172
171
|
Reset
|
|
173
172
|
</button>
|
|
174
173
|
|
|
@@ -181,7 +180,7 @@ function MyComponent() {
|
|
|
181
180
|
|
|
182
181
|
<details>
|
|
183
182
|
<Summary>Example</Summary>
|
|
184
|
-
|
|
183
|
+
|
|
185
184
|
```typescript
|
|
186
185
|
export const form: Form = {
|
|
187
186
|
name: '',
|
|
@@ -205,19 +204,19 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
205
204
|
import { form } from './form';
|
|
206
205
|
|
|
207
206
|
function TagsComponent() {
|
|
208
|
-
// `
|
|
209
|
-
const {
|
|
207
|
+
// `urlState` will infer from Form type!
|
|
208
|
+
const { urlState, setUrl } = useUrlState({ defaultState: form });
|
|
210
209
|
|
|
211
210
|
const onChangeTags = React.useCallback(
|
|
212
211
|
(tag: (typeof tags)[number]) => {
|
|
213
|
-
|
|
212
|
+
setUrl((curr) => ({
|
|
214
213
|
...curr,
|
|
215
214
|
tags: curr.tags.find((t) => t.id === tag.id)
|
|
216
215
|
? curr.tags.filter((t) => t.id !== tag.id)
|
|
217
216
|
: curr.tags.concat(tag),
|
|
218
217
|
}));
|
|
219
218
|
},
|
|
220
|
-
[
|
|
219
|
+
[setUrl],
|
|
221
220
|
);
|
|
222
221
|
|
|
223
222
|
return (
|
|
@@ -226,7 +225,7 @@ function TagsComponent() {
|
|
|
226
225
|
<div className="flex flex-wrap gap-2">
|
|
227
226
|
{tags.map((tag) => (
|
|
228
227
|
<Tag
|
|
229
|
-
active={!!
|
|
228
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
230
229
|
text={tag.value.text}
|
|
231
230
|
onClick={() => onChangeTags(tag)}
|
|
232
231
|
key={tag.id}
|
|
@@ -257,26 +256,25 @@ const tags = [
|
|
|
257
256
|
[Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
|
|
258
257
|
</details>
|
|
259
258
|
|
|
260
|
-
|
|
261
|
-
#### Auto sync state
|
|
259
|
+
#### Update state only and sync to URL manually
|
|
262
260
|
|
|
263
261
|
<details>
|
|
264
262
|
<Summary>Example</Summary>
|
|
265
263
|
|
|
266
264
|
```typescript
|
|
267
|
-
|
|
265
|
+
|
|
268
266
|
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
|
|
269
267
|
React.useEffect(() => {
|
|
270
268
|
clearTimeout(timer.current);
|
|
271
269
|
timer.current = setTimeout(() => {
|
|
272
270
|
// will compare state by content not by reference and fire update only for new values
|
|
273
|
-
|
|
271
|
+
setUrl(urlState);
|
|
274
272
|
}, 500);
|
|
275
273
|
|
|
276
274
|
return () => {
|
|
277
275
|
clearTimeout(timer.current);
|
|
278
276
|
};
|
|
279
|
-
}, [
|
|
277
|
+
}, [urlState, setUrl]);
|
|
280
278
|
```
|
|
281
279
|
|
|
282
280
|
Syncing state `onBlur` will be more aligned with real world usage.
|
|
@@ -287,13 +285,11 @@ Syncing state `onBlur` will be more aligned with real world usage.
|
|
|
287
285
|
|
|
288
286
|
</details>
|
|
289
287
|
|
|
290
|
-
|
|
291
|
-
|
|
292
288
|
#### With server side rendering
|
|
293
289
|
|
|
294
290
|
<details>
|
|
295
291
|
<Summary>Example</Summary>
|
|
296
|
-
|
|
292
|
+
|
|
297
293
|
```typescript
|
|
298
294
|
export default async function Home({ searchParams }: { searchParams: object }) {
|
|
299
295
|
return (
|
|
@@ -308,7 +304,7 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
308
304
|
import { form } from './form';
|
|
309
305
|
|
|
310
306
|
const Form = ({ searchParams }: { searchParams: object }) => {
|
|
311
|
-
const {
|
|
307
|
+
const { urlState, setState, setUrl } = useUrlState({ defaultState: form, searchParams });
|
|
312
308
|
}
|
|
313
309
|
```
|
|
314
310
|
|
|
@@ -362,6 +358,7 @@ export default async function Layout({
|
|
|
362
358
|
|
|
363
359
|
|
|
364
360
|
```
|
|
361
|
+
|
|
365
362
|
</details>
|
|
366
363
|
|
|
367
364
|
#### With arbitrary state shape (not recommended)
|
|
@@ -376,12 +373,11 @@ import { useUrlState } from 'state-in-url/next';
|
|
|
376
373
|
const someObj = {};
|
|
377
374
|
|
|
378
375
|
function SettingsComponent() {
|
|
379
|
-
const {
|
|
376
|
+
const { urlState, setUrl, setState } = useUrlState<object>(someObj);
|
|
380
377
|
}
|
|
381
378
|
```
|
|
382
|
-
</details>
|
|
383
|
-
|
|
384
379
|
|
|
380
|
+
</details>
|
|
385
381
|
|
|
386
382
|
## useUrlState hook for React-Router
|
|
387
383
|
|
|
@@ -414,18 +410,18 @@ import { useUrlState } from 'state-in-url/react-router';
|
|
|
414
410
|
import { form } from './form';
|
|
415
411
|
|
|
416
412
|
function TagsComponent() {
|
|
417
|
-
const {
|
|
413
|
+
const { urlState, setUrl, setUrlState } = useUrlState({ defaultState: form });
|
|
418
414
|
|
|
419
415
|
const onChangeTags = React.useCallback(
|
|
420
416
|
(tag: (typeof tags)[number]) => {
|
|
421
|
-
|
|
417
|
+
setUrl((curr) => ({
|
|
422
418
|
...curr,
|
|
423
419
|
tags: curr.tags.find((t) => t.id === tag.id)
|
|
424
420
|
? curr.tags.filter((t) => t.id !== tag.id)
|
|
425
421
|
: curr.tags.concat(tag),
|
|
426
422
|
}));
|
|
427
423
|
},
|
|
428
|
-
[
|
|
424
|
+
[setUrl],
|
|
429
425
|
);
|
|
430
426
|
|
|
431
427
|
return (
|
|
@@ -434,7 +430,7 @@ function TagsComponent() {
|
|
|
434
430
|
<div className="flex flex-wrap gap-2">
|
|
435
431
|
{tags.map((tag) => (
|
|
436
432
|
<Tag
|
|
437
|
-
active={!!
|
|
433
|
+
active={!!urlState.tags.find((t) => t.id === tag.id)}
|
|
438
434
|
text={tag.value.text}
|
|
439
435
|
onClick={() => onChangeTags(tag)}
|
|
440
436
|
key={tag.id}
|
|
@@ -442,6 +438,12 @@ function TagsComponent() {
|
|
|
442
438
|
))}
|
|
443
439
|
</div>
|
|
444
440
|
</Field>
|
|
441
|
+
|
|
442
|
+
<input value={urlState.name}
|
|
443
|
+
onChange={(ev) => { setUrlState(curr => ({ ...curr, name: ev.target.value })) }}
|
|
444
|
+
// Can update state immediately but sync change to url as needed
|
|
445
|
+
onBlur={() => setUrl()}
|
|
446
|
+
/>
|
|
445
447
|
</div>
|
|
446
448
|
);
|
|
447
449
|
}
|
|
@@ -506,8 +508,6 @@ function SettingsComponent() {
|
|
|
506
508
|
- Define your state shape as a constant
|
|
507
509
|
- Use TypeScript for enhanced type safety and autocomplete
|
|
508
510
|
- Avoid storing sensitive information in URL parameters (SSN, API keys etc)
|
|
509
|
-
- Use `updateState` for frequent updates and `updateUrl` to sync changes to url
|
|
510
|
-
- Use `Suspence` to wrap client components in Next.js
|
|
511
511
|
- Use this [extension](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) for readable TS errors
|
|
512
512
|
|
|
513
513
|
## Gothas
|
|
@@ -1,55 +1,69 @@
|
|
|
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
|
|
8
|
+
* @param {boolean} [useHistory] use window.history for navigation, no _rsc requests https://github.com/vercel/next.js/discussions/59167
|
|
8
9
|
*
|
|
9
10
|
* * Example:
|
|
10
11
|
* ```ts
|
|
11
12
|
* export const form = { name: '', age: 0 };
|
|
12
|
-
* const {
|
|
13
|
+
* const { urlState, setState, setUrl } = useUrlState({ defaultState: form });
|
|
13
14
|
* // for nextjs seerver components
|
|
14
|
-
* // const {
|
|
15
|
+
* // const { urlState, setState, setUrl } = useUrlState({ defaultState: form, searchParams });
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
+
* setState({ name: 'test' });
|
|
17
18
|
* // by default it's uses router.push with scroll: false
|
|
18
|
-
*
|
|
19
|
+
* setUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
19
20
|
* // similar to React.useState
|
|
20
|
-
*
|
|
21
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
|
|
21
22
|
* ```
|
|
22
23
|
*
|
|
23
24
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#api}
|
|
24
25
|
*/
|
|
25
|
-
export declare function useUrlState<T extends JSONCompatible>({ defaultState, searchParams, ...opts }: {
|
|
26
|
+
export declare function useUrlState<T extends JSONCompatible>({ defaultState, searchParams, useHistory, ...opts }: {
|
|
26
27
|
defaultState: T;
|
|
27
28
|
searchParams?: object;
|
|
28
29
|
replace?: boolean;
|
|
29
30
|
scroll?: boolean;
|
|
31
|
+
useHistory?: boolean;
|
|
30
32
|
}): {
|
|
31
33
|
/**
|
|
32
34
|
* * Example:
|
|
33
35
|
* ```ts
|
|
34
|
-
*
|
|
36
|
+
* setState({ name: 'test' });
|
|
35
37
|
* // or
|
|
36
|
-
*
|
|
38
|
+
* setState(curr => ({ ...curr, name: 'test' }) );
|
|
37
39
|
* ```
|
|
38
40
|
*
|
|
39
41
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updatestate}
|
|
40
42
|
*/
|
|
43
|
+
setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
44
|
+
/**
|
|
45
|
+
* @deprecated use `setState`
|
|
46
|
+
*/
|
|
41
47
|
updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
42
48
|
/**
|
|
43
49
|
* * Example:
|
|
44
50
|
* ```ts
|
|
45
|
-
*
|
|
51
|
+
* setUrl({ name: 'test' });
|
|
46
52
|
* // or
|
|
47
|
-
*
|
|
53
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
|
|
48
54
|
* ```
|
|
49
55
|
*
|
|
50
56
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updateurl}
|
|
51
57
|
*/
|
|
58
|
+
setUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
|
|
59
|
+
/**
|
|
60
|
+
* @deprecated use `setUrl`
|
|
61
|
+
*/
|
|
52
62
|
updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
|
|
63
|
+
urlState: DeepReadonly<DeepReadonly<T>>;
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated use `urlState`
|
|
66
|
+
*/
|
|
53
67
|
state: DeepReadonly<DeepReadonly<T>>;
|
|
54
68
|
getState: () => T;
|
|
55
69
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRouter as t,useSearchParams as e}from"next/navigation";import
|
|
1
|
+
import{useRouter as t,useSearchParams as e}from"next/navigation";import r from"react";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as o,isSSR as m,filterUnknownParams as u,filterUnknownParamsClient as i}from"../../utils.mjs";function l({defaultState:l,searchParams:c,useHistory:n,...f}){const S=void 0===n||n?o:t(),{state:d,updateState:j,updateUrl:U,getState:b}=s(l,S,(({parse:t})=>m()?a(u(l,c),l):t(i(l)))),g=r.useCallback(((t,e)=>{const r={...p,...f,...e};U(t,r)}),[U,f]),v=e();return r.useEffect((()=>{j(u(l,a(Object.fromEntries([...v.entries()]),l)))}),[v]),{setState:j,updateState:j,setUrl:g,updateUrl:g,urlState:d,state:d,getState:b}}const p={replace:!0,scroll:!1};export{l as useUrlState};
|
|
@@ -1,52 +1,66 @@
|
|
|
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
|
+
* @param {boolean} [useHistory] use window.history for navigation
|
|
8
9
|
* * Example:
|
|
9
10
|
* ```ts
|
|
10
11
|
* export const form = { name: '', age: 0 };
|
|
11
|
-
* const {
|
|
12
|
+
* const { urlState, setState, setUrl } = useUrlState({ defaultState: form, replace: false, preventScrollReset: false });
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* setState({ name: 'test' });
|
|
15
|
+
* setUrl({ name: 'test' }, { replace: true });
|
|
15
16
|
* // similar to React.useState
|
|
16
|
-
*
|
|
17
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
|
|
17
18
|
* ```
|
|
18
19
|
*
|
|
19
20
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#api}
|
|
20
21
|
*/
|
|
21
|
-
export declare function useUrlState<T extends JSONCompatible>({ defaultState, ...initOpts }: {
|
|
22
|
+
export declare function useUrlState<T extends JSONCompatible>({ defaultState, useHistory, ...initOpts }: {
|
|
22
23
|
defaultState: T;
|
|
24
|
+
useHistory?: boolean;
|
|
23
25
|
} & NavigateOptions): {
|
|
24
26
|
/**
|
|
25
27
|
* * Example:
|
|
26
28
|
* ```ts
|
|
27
|
-
*
|
|
29
|
+
* setUrlState({ name: 'test' });
|
|
28
30
|
* // or
|
|
29
|
-
*
|
|
31
|
+
* setUrlState(curr => ({ ...curr, name: 'test' }) );
|
|
30
32
|
* // can pass optional React-Router `NavigateOptions`
|
|
31
|
-
*
|
|
33
|
+
* setUrlState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
|
|
32
34
|
* ```
|
|
33
35
|
*
|
|
34
36
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
|
|
35
37
|
*/
|
|
38
|
+
setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
39
|
+
/**
|
|
40
|
+
* @deprecated use `setState`
|
|
41
|
+
*/
|
|
36
42
|
updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
37
43
|
/**
|
|
38
44
|
* * Example:
|
|
39
45
|
* ```ts
|
|
40
|
-
*
|
|
46
|
+
* setUrl({ name: 'test' });
|
|
41
47
|
* // or
|
|
42
|
-
*
|
|
48
|
+
* setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
|
|
43
49
|
* // can pass optional React-Router `NavigateOptions`
|
|
44
|
-
*
|
|
50
|
+
* setState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
|
|
45
51
|
* ```
|
|
46
52
|
*
|
|
47
53
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
|
|
48
54
|
*/
|
|
55
|
+
setUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated use `setUrl`
|
|
58
|
+
*/
|
|
49
59
|
updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
|
|
60
|
+
urlState: DeepReadonly<DeepReadonly<T>>;
|
|
61
|
+
/**
|
|
62
|
+
* @deprecated use `urlState`
|
|
63
|
+
*/
|
|
50
64
|
state: DeepReadonly<DeepReadonly<T>>;
|
|
51
65
|
getState: () => T;
|
|
52
66
|
};
|
|
@@ -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{
|
|
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{routerHistory as o,filterUnknownParamsClient as u,assignValue as m,filterUnknownParams as p}from"../../utils.mjs";function l({defaultState:l,useHistory:S,...f}){const i=e(),n=t.useMemo((()=>S?o:{replace:(t,e)=>i(t,{...c,...f,...e}),push:(t,e)=>i(t,{...c,...f,...e})}),[i,f]),{state:d,updateState:j,updateUrl:U,getState:b}=s(l,n,(({parse:t})=>t(u(l)))),g=t.useCallback(((t,e)=>{const r={...c,...f,...e};U(t,r)}),[f]),[B]=r();return t.useEffect((()=>{j(m(l,d,p(l,a(Object.fromEntries([...B.entries()]),l))))}),[B]),{setState:j,updateState:j,setUrl:g,updateUrl:g,urlState:d,state:d,getState:b}}const c={replace:!0,preventScrollReset:!0};export{l as useUrlState};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useUrlEncode } from "../useUrlEncode";
|
|
2
|
-
import { type DeepReadonly, type JSONCompatible } from "../utils";
|
|
2
|
+
import { type DeepReadonly, type JSONCompatible, type Router } from "../utils";
|
|
3
3
|
/**
|
|
4
4
|
* A custom React hook to create custom hooks.
|
|
5
5
|
*
|
|
@@ -35,8 +35,4 @@ interface OptionsObject {
|
|
|
35
35
|
export interface Options extends OptionsObject {
|
|
36
36
|
replace?: boolean;
|
|
37
37
|
}
|
|
38
|
-
type Router = {
|
|
39
|
-
push: (href: string, opts: object) => void;
|
|
40
|
-
replace: (href: string, opts: object) => void;
|
|
41
|
-
};
|
|
42
38
|
export {};
|
|
@@ -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:h}=o(s),{state:l,getState:m,setState:p}=n(s,(()=>i?.({parse:u})||s));e((()=>{const t=()=>{const t=u(r(s));p(t)};return window.addEventListener(a,t),()=>{window.removeEventListener(a,t)}}),[p]);const d=t.useRef([]),w=t.useCallback(((t,e)=>{const n="function"==typeof t?t(m()):t?{...m(),...t}:m(),o=h(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;p(n);const{replace:i,...u}=e||{};d.current.push([i?"replace":"push",r,u]),1===d.current.length&&queueMicrotask((()=>{for(;d.current.length;){const t=d.current.shift();t&&t?.[1]!==a?.[1]&&(a=t)}const[t,e,n]=a||{};a=void 0,t&&c[t](e,n)}))}),[c,h,m]);return{updateState:p,updateUrl:w,state:l,getState:m}}const a="popstate";export{s as useUrlStateBase};
|
package/dist/utils.d.ts
CHANGED
|
@@ -26,3 +26,8 @@ export declare const isEqual: (val1: unknown, val2: unknown) => boolean;
|
|
|
26
26
|
export declare function filterUnknownParamsClient<T extends object>(shape: T): string;
|
|
27
27
|
export declare function filterUnknownParams<T extends object>(shape: T, searchParams?: object): T;
|
|
28
28
|
export declare function assignValue<T extends object>(shape: T, curr: Partial<T>, newVal: Partial<T>): T;
|
|
29
|
+
export interface Router {
|
|
30
|
+
push: (href: string, opts: object) => void;
|
|
31
|
+
replace: (href: string, opts: object) => void;
|
|
32
|
+
}
|
|
33
|
+
export declare const routerHistory: Router;
|
package/dist/utils.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),
|
|
1
|
+
const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),o=t instanceof Date;return(e?"null":o&&"date")||r&&"array"||!e&&!o&&!r&&"object"===n&&"object"||n},n=()=>"undefined"==typeof window,e=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",o=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function i(t){const n=new URLSearchParams;return s(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function c(t,n){return Object.fromEntries(s(t,Object.entries(n||{})))}function s(t,n){const e=Object.keys(t);return n.filter((([t])=>e.includes(t))).map((([t,n])=>[t.replaceAll("+"," "),n]))}function a(t,n,e){const r=Object.assign({},t,n);return Object.entries(t).forEach((([n])=>{const o=n,i=void 0!==e[o];r[o]=i?e[o]:t[o]})),r}const l={push:t=>{window&&window.history.pushState(null,"",t)},replace:t=>{window&&window.history.replaceState(null,"",t)}};export{a as assignValue,c as filterUnknownParams,i as filterUnknownParamsClient,e as getParams,o as isEqual,n as isSSR,l as routerHistory,t as typeOf};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.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
5
|
"homepage": "https://state-in-url.dev",
|
|
6
6
|
"keywords": [
|
|
@@ -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",
|