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 CHANGED
@@ -22,7 +22,7 @@
22
22
 
23
23
  ![演示-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
24
24
 
25
- <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">演示</a> |
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
  ![데모-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
27
27
 
28
28
 
29
- <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">데모</a> |
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
  ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/blob/master/assets/Demo-gif.gif)
33
33
 
34
- <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
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 `updateUrl` will use `rounter.push` or `router.replace`, default replace=true
146
- // const { state, updateState, updateUrl } = useUrlState({ defaultState: userState, searchParams, replace: false });
147
- const { state, updateState, updateUrl } = useUrlState({ defaultState: userState });
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
- // state.name = 'John' // <- error
152
+ // urlState.name = 'John' // <- error
151
153
 
152
154
  return (
153
155
  <div>
154
- <input value={state.name}
155
- onChange={(ev) => { updateState({ name: ev.target.value }) }}
156
- onBlur={() => updateUrl()}
156
+ <input value={urlState.name}
157
+ onChange={(ev) => { setUrl({ name: ev.target.value }) }}
157
158
  />
158
- <input value={state.age}
159
- onChange={(ev) => { updateState({ age: +ev.target.value }) }}
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={state.name}
165
- onChange={(ev) => { updateState(curr => ({ ...curr, name: ev.target.value })) }}
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={() => updateUrl(userState)}>
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
- // `state` will infer from Form type!
205
- const { state, updateUrl } = useUrlState({ defaultState: form });
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
- updateUrl((curr) => ({
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
- [updateUrl],
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={!!state.tags.find((t) => t.id === tag.id)}
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
- #### Auto sync state
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
- updateUrl(state);
269
+ setUrl(urlState);
264
270
  }, 500);
265
271
 
266
272
  return () => {
267
273
  clearTimeout(timer.current);
268
274
  };
269
- }, [state, updateUrl]);
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 { state, updateState, updateUrl } = useUrlState({ defaultState: form, searchParams });
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
- 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.
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 { state, updateUrl, updateState } = useUrlState<object>(someObj);
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 { state, updateUrl } = useUrlState({ defaultState: form });
411
+ const { urlState, setUrl } = useUrlState({ defaultState: form });
390
412
 
391
413
  const onChangeTags = React.useCallback(
392
414
  (tag: (typeof tags)[number]) => {
393
- updateUrl((curr) => ({
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
- [updateUrl],
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={!!state.tags.find((t) => t.id === tag.id)}
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 `state`, `updateState`, and `updateUrl` functions
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 { state, updateState, updateUrl } = useUrlState({ defaultState: form });
12
+ * const { urlState, setState, setUrl } = useUrlState({ defaultState: form });
13
13
  * // for nextjs seerver components
14
- * // const { state, updateState, updateUrl } = useUrlState({ defaultState: form, searchParams });
14
+ * // const { urlState, setState, setUrl } = useUrlState({ defaultState: form, searchParams });
15
15
  *
16
- * updateState({ name: 'test' });
16
+ * setState({ name: 'test' });
17
17
  * // by default it's uses router.push with scroll: false
18
- * updateUrl({ name: 'test' }, { replace: true, scroll: true });
18
+ * setUrl({ name: 'test' }, { replace: true, scroll: true });
19
19
  * // similar to React.useState
20
- * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
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
- * updateState({ name: 'test' });
34
+ * setState({ name: 'test' });
35
35
  * // or
36
- * updateState(curr => ({ ...curr, name: 'test' }) );
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
- * updateUrl({ name: 'test' });
49
+ * setUrl({ name: 'test' });
46
50
  * // or
47
- * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
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 p}from"../../utils.mjs";function u({defaultState:u,searchParams:i,...l}){const n=t(),{state:f,updateState:S,updateUrl:d,getState:j}=s(u,n,(({parse:t})=>o()?r(m(u,i),u):t(p(u)))),U=a.useCallback(((t,e)=>{const a={...c,...l,...e};d(t,a)}),[d,l]),b=e();return a.useEffect((()=>{S(m(u,r(Object.fromEntries([...b.entries()]),u)))}),[b]),{updateState:S,updateUrl:U,state:f,getState:j}}const c={replace:!0,scroll:!1};export{u as useUrlState};
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 `state`, `updateState`, and `updateUrl` functions
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 { state, updateState, updateUrl } = useUrlState({ defaultState: form, replace: false, preventScrollReset: false });
11
+ * const { urlState, setState, setUrl } = useUrlState({ defaultState: form, replace: false, preventScrollReset: false });
12
12
  *
13
- * updateState({ name: 'test' });
14
- * updateUrl({ name: 'test' }, { replace: true });
13
+ * setState({ name: 'test' });
14
+ * setUrl({ name: 'test' }, { replace: true });
15
15
  * // similar to React.useState
16
- * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
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
- * updateState({ name: 'test' });
27
+ * setUrlState({ name: 'test' });
28
28
  * // or
29
- * updateState(curr => ({ ...curr, name: 'test' }) );
29
+ * setUrlState(curr => ({ ...curr, name: 'test' }) );
30
30
  * // can pass optional React-Router `NavigateOptions`
31
- * updateState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
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
- * updateUrl({ name: 'test' });
44
+ * setUrl({ name: 'test' });
41
45
  * // or
42
- * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
46
+ * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
43
47
  * // can pass optional React-Router `NavigateOptions`
44
- * updateState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
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,...l}){const f=e(),i=t.useMemo((()=>({replace:(t,e)=>f(t,{...c,...l,...e}),push:(t,e)=>f(t,{...c,...l,...e})})),[f,l]),{state:n,updateState:S,updateUrl:d,getState:j}=s(u,i,(({parse:t})=>t(o(u)))),U=t.useCallback(((t,e)=>{const r={...c,...l,...e};d(t,r)}),[l]),[b]=r();return t.useEffect((()=>{S(m(u,n,p(u,a(Object.fromEntries([...b.entries()]),u))))}),[b]),{updateState:S,updateUrl:U,state:n,getState:j}}const c={replace:!0,preventScrollReset:!0};export{u as useUrlState};
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 o}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as n}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as a}from"../utils.mjs";function s(s,c,i){const{parse:m,stringify:p}=n(s),{state:w,getState:d,setState:u}=o(s,(()=>i?.({parse:m})||s));e((()=>{const t=()=>{const t=m(a(s));u(t)};return window.addEventListener(r,t),()=>{window.removeEventListener(r,t)}}),[u]);const l=t.useCallback(((t,e)=>{const o=`${window.location.pathname}${window.location.search}${window.location.hash}`,n="function"==typeof t,a=function(t){const e=Object.keys(t),o=window.location.search,n=new URLSearchParams(o),a=new URLSearchParams;return n.forEach(((t,o)=>!e.includes(o)&&a.set(o,t))),a}(s),r=n?t(d()):t?{...d(),...t}:d(),i=p(r,a);u(r);const m=`${window.location.pathname}${i.length?"?":""}${i}${window.location.hash}`;if(o!==m){const{replace:t,...o}=e||{};c[t?"replace":"push"](m,{...o})}}),[c,p,d]);return{updateState:u,updateUrl:l,state:w,getState:d}}const r="popstate";export{s as useUrlStateBase};
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.6",
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-asmyshlyaev177.vercel.app",
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.45.3",
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.45.3",
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",