state-in-url 5.1.0 → 6.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 CHANGED
@@ -99,26 +99,46 @@ This library is a good alternative for NUQS.
99
99
 
100
100
  ## Table of content
101
101
 
102
- - [Installation](#installation)
103
- - [useUrlState hook](#useurlstate)
104
- - - [Next.js](#useurlstate-hook-for-nextjs)
105
- - - [Remix](#useurlstate-hook-for-remixjs)
106
- - - [React-Router](#useurlstate-hook-for-react-router)
107
- - [Other helpers](#other-hooks-and-helpers)
108
- - - [`useUrlStateBase` for other routers](#useurlstatebase-hook-for-others-routers)
109
- - - [`useSharedState` hook for React.js/Next.js](#usesharedstate-hook-for-reactjs)
110
- - - [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
111
- - - [`encodeState` and `decodeState` for pure JS usage](#encodestate-and-decodestate-helpers)
112
- - - [Low-level `encode` and `decode` functions](#encode-and-decode-helpers)
113
- - [Best practices](#best-practices)
114
- - - [Gotchas](#gotchas)
115
- - [Other](#other)
116
- - - [Roadmap](#roadmap)
117
- - - [Contributing](#contribute-andor-run-locally)
118
- - - [Contact & Support](#contact--support)
119
- - - [Changelog](#changelog)
120
- - - [License](#license)
121
- - - [Inspiration](#inspiration)
102
+ - [State in url](#state-in-url)
103
+ - [Demo](#demo)
104
+ - [Why use `state-in-url`?](#why-use-state-in-url)
105
+ - [Use cases](#use-cases)
106
+ - [Features](#features)
107
+ - [Table of content](#table-of-content)
108
+ - [installation](#installation)
109
+ - [1. Install package](#1-install-package)
110
+ - [2. Edit tsconfig.json](#2-edit-tsconfigjson)
111
+ - [useUrlState](#useurlstate)
112
+ - [useUrlState hook for Next.js](#useurlstate-hook-for-nextjs)
113
+ - [Usage examples](#usage-examples)
114
+ - [Basic](#basic)
115
+ - [With server side rendering](#with-server-side-rendering)
116
+ - [Using hook in `layout` component](#using-hook-in-layout-component)
117
+ - [With arbitrary state shape (not recommended)](#with-arbitrary-state-shape-not-recommended)
118
+ - [useUrlState hook for Remix.js](#useurlstate-hook-for-remixjs)
119
+ - [Example](#example)
120
+ - [useUrlState hook for React-Router](#useurlstate-hook-for-react-router)
121
+ - [Example](#example-1)
122
+ - [Recipes](#recipes)
123
+ - [Custom hook to work with slice of state conveniently](#custom-hook-to-work-with-slice-of-state-conveniently)
124
+ - [With complex state shape](#with-complex-state-shape)
125
+ - [Update state only and sync to URL manually](#update-state-only-and-sync-to-url-manually)
126
+ - [Other hooks and helpers](#other-hooks-and-helpers)
127
+ - [`useUrlStateBase` hook for others routers](#useurlstatebase-hook-for-others-routers)
128
+ - [`useSharedState` hook for React.js](#usesharedstate-hook-for-reactjs)
129
+ - [`useUrlEncode` hook for React.js](#useurlencode-hook-for-reactjs)
130
+ - [`encodeState` and `decodeState` helpers](#encodestate-and-decodestate-helpers)
131
+ - [`encode` and `decode` helpers](#encode-and-decode-helpers)
132
+ - [Best Practices](#best-practices)
133
+ - [Gotchas](#gotchas)
134
+ - [Other](#other)
135
+ - [Contribute and/or run locally](#contribute-andor-run-locally)
136
+ - [Roadmap](#roadmap)
137
+ - [Contact \& Support](#contact--support)
138
+ - [Changelog](#changelog)
139
+ - [Mentions](#mentions)
140
+ - [License](#license)
141
+ - [Inspiration](#inspiration)
122
142
 
123
143
  ## installation
124
144
 
@@ -175,7 +195,7 @@ import { userState } from './userState';
175
195
  function MyComponent() {
176
196
  // can pass `replace` arg, it's control will `setUrl` will use `rounter.push` or `router.replace`, default replace=true
177
197
  // can pass `searchParams` from server components, pass `useHistory: false` if you need to fetch smt in the server component
178
- const { urlState, setUrl, setState, reset } = useUrlState(userState);
198
+ const { urlState, setUrl, setState } = useUrlState(userState);
179
199
 
180
200
  return (
181
201
  <div>
@@ -194,7 +214,7 @@ function MyComponent() {
194
214
  onBlur={() => setUrl()}
195
215
  />
196
216
 
197
- <button onClick={reset}>
217
+ <button onClick={() => setUrl((_, initial) => initial)}>
198
218
  Reset
199
219
  </button>
200
220
 
@@ -203,164 +223,6 @@ function MyComponent() {
203
223
  }
204
224
  ```
205
225
 
206
- ##### Custom hook to work with slice of state conveniently
207
- <details>
208
- <Summary>Example</Summary>
209
-
210
- ```typescript
211
- 'use client';
212
-
213
- import React from 'react';
214
- import { useUrlState } from 'state-in-url/next';
215
-
216
- const form: Form = {
217
- name: '',
218
- age: undefined,
219
- agree_to_terms: false,
220
- tags: [],
221
- };
222
-
223
- type Form = {
224
- name: string;
225
- age?: number;
226
- agree_to_terms: boolean;
227
- tags: {id: string; value: {text: string; time: Date } }[];
228
- };
229
-
230
- export const useFormState = ({ searchParams }: { searchParams?: object }) => {
231
- const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
232
- searchParams,
233
- });
234
-
235
- // first navigation will push new history entry
236
- // all following will just replace that entry
237
- // this way will have history with only 2 entries - ['/url', '/url?key=param']
238
-
239
- const replace = React.useRef(false);
240
- const setUrl = React.useCallback((
241
- state: Parameters<typeof setUrlBase>[0],
242
- opts?: Parameters<typeof setUrlBase>[1]
243
- ) => {
244
- setUrlBase(state, { replace: replace.current, ...opts });
245
- replace.current = true;
246
- }, [setUrlBase]);
247
-
248
- return { urlState, setUrl, resetUrl: reset };
249
- };
250
- ```
251
- </details>
252
-
253
- <hr />
254
-
255
- ##### With complex state shape
256
-
257
- <details>
258
- <Summary>Example</Summary>
259
-
260
- ```typescript
261
- export const form: Form = {
262
- name: '',
263
- age: undefined,
264
- agree_to_terms: false,
265
- tags: [],
266
- };
267
-
268
- type Form = {
269
- name: string;
270
- age?: number;
271
- agree_to_terms: boolean;
272
- tags: { id: string; value: { text: string; time: Date } }[];
273
- };
274
- ```
275
-
276
- ```typescript
277
- 'use client'
278
- import { useUrlState } from 'state-in-url/next';
279
-
280
- import { form } from './form';
281
-
282
- function TagsComponent() {
283
- // `urlState` will infer from Form type!
284
- const { urlState, setUrl } = useUrlState(form);
285
-
286
- const onChangeTags = React.useCallback(
287
- (tag: (typeof tags)[number]) => {
288
- setUrl((curr) => ({
289
- ...curr,
290
- tags: curr.tags.find((t) => t.id === tag.id)
291
- ? curr.tags.filter((t) => t.id !== tag.id)
292
- : curr.tags.concat(tag),
293
- }));
294
- },
295
- [setUrl],
296
- );
297
-
298
- return (
299
- <div>
300
- <Field text="Tags">
301
- <div className="flex flex-wrap gap-2">
302
- {tags.map((tag) => (
303
- <Tag
304
- active={!!urlState.tags.find((t) => t.id === tag.id)}
305
- text={tag.value.text}
306
- onClick={() => onChangeTags(tag)}
307
- key={tag.id}
308
- />
309
- ))}
310
- </div>
311
- </Field>
312
- </div>
313
- );
314
- }
315
-
316
- const tags = [
317
- {
318
- id: '1',
319
- value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') },
320
- },
321
- {
322
- id: '2',
323
- value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') },
324
- },
325
- {
326
- id: '3',
327
- value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') },
328
- },
329
- ];
330
- ```
331
-
332
- [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
333
- </details>
334
-
335
- ##### Update state only and sync to URL manually
336
-
337
- <details>
338
- <Summary>Example</Summary>
339
-
340
- ```typescript
341
-
342
- const timer = React.useRef(0 as unknown as NodeJS.Timeout);
343
- React.useEffect(() => {
344
- clearTimeout(timer.current);
345
- timer.current = setTimeout(() => {
346
- // will compare state by content not by reference and fire update only for new values
347
- setUrl(urlState);
348
- }, 500);
349
-
350
- return () => {
351
- clearTimeout(timer.current);
352
- };
353
- }, [urlState, setUrl]);
354
- ```
355
-
356
- Syncing state `onBlur` will be more aligned with real world usage.
357
-
358
- ```typescript
359
- <input onBlur={() => updateUrl()} .../>
360
- ```
361
-
362
- </details>
363
-
364
226
  ##### With server side rendering
365
227
 
366
228
  <details>
@@ -569,6 +431,8 @@ type Form = {
569
431
 
570
432
  ```typescript
571
433
  import { useUrlState } from 'state-in-url/react-router';
434
+ // for react-router v6
435
+ // import { useUrlState } from 'state-in-url/react-router6';
572
436
 
573
437
  import { form } from './form';
574
438
 
@@ -629,6 +493,165 @@ const tags = [
629
493
 
630
494
  [Example code](packages/example-react-router6/src/Form-for-test.tsx)
631
495
 
496
+ ## Recipes
497
+ ##### Custom hook to work with slice of state conveniently
498
+ <details>
499
+ <Summary>Example</Summary>
500
+
501
+ ```typescript
502
+ 'use client';
503
+
504
+ import React from 'react';
505
+ import { useUrlState } from 'state-in-url/next';
506
+
507
+ const form: Form = {
508
+ name: '',
509
+ age: undefined,
510
+ agree_to_terms: false,
511
+ tags: [],
512
+ };
513
+
514
+ type Form = {
515
+ name: string;
516
+ age?: number;
517
+ agree_to_terms: boolean;
518
+ tags: {id: string; value: {text: string; time: Date } }[];
519
+ };
520
+
521
+ export const useFormState = ({ searchParams }: { searchParams?: object }) => {
522
+ const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
523
+ searchParams,
524
+ });
525
+
526
+ // first navigation will push new history entry
527
+ // all following will just replace that entry
528
+ // this way will have history with only 2 entries - ['/url', '/url?key=param']
529
+
530
+ const replace = React.useRef(false);
531
+ const setUrl = React.useCallback((
532
+ state: Parameters<typeof setUrlBase>[0],
533
+ opts?: Parameters<typeof setUrlBase>[1]
534
+ ) => {
535
+ setUrlBase(state, { replace: replace.current, ...opts });
536
+ replace.current = true;
537
+ }, [setUrlBase]);
538
+
539
+ return { urlState, setUrl, resetUrl: reset };
540
+ };
541
+ ```
542
+ </details>
543
+
544
+ <hr />
545
+
546
+ ##### With complex state shape
547
+
548
+ <details>
549
+ <Summary>Example</Summary>
550
+
551
+ ```typescript
552
+ export const form: Form = {
553
+ name: '',
554
+ age: undefined,
555
+ agree_to_terms: false,
556
+ tags: [],
557
+ };
558
+
559
+ type Form = {
560
+ name: string;
561
+ age?: number;
562
+ agree_to_terms: boolean;
563
+ tags: { id: string; value: { text: string; time: Date } }[];
564
+ };
565
+ ```
566
+
567
+ ```typescript
568
+ 'use client'
569
+ import { useUrlState } from 'state-in-url/next';
570
+
571
+ import { form } from './form';
572
+
573
+ function TagsComponent() {
574
+ // `urlState` will infer from Form type!
575
+ const { urlState, setUrl } = useUrlState(form);
576
+
577
+ const onChangeTags = React.useCallback(
578
+ (tag: (typeof tags)[number]) => {
579
+ setUrl((curr) => ({
580
+ ...curr,
581
+ tags: curr.tags.find((t) => t.id === tag.id)
582
+ ? curr.tags.filter((t) => t.id !== tag.id)
583
+ : curr.tags.concat(tag),
584
+ }));
585
+ },
586
+ [setUrl],
587
+ );
588
+
589
+ return (
590
+ <div>
591
+ <Field text="Tags">
592
+ <div className="flex flex-wrap gap-2">
593
+ {tags.map((tag) => (
594
+ <Tag
595
+ active={!!urlState.tags.find((t) => t.id === tag.id)}
596
+ text={tag.value.text}
597
+ onClick={() => onChangeTags(tag)}
598
+ key={tag.id}
599
+ />
600
+ ))}
601
+ </div>
602
+ </Field>
603
+ </div>
604
+ );
605
+ }
606
+
607
+ const tags = [
608
+ {
609
+ id: '1',
610
+ value: { text: 'React.js', time: new Date('2024-07-17T04:53:17.000Z') },
611
+ },
612
+ {
613
+ id: '2',
614
+ value: { text: 'Next.js', time: new Date('2024-07-18T04:53:17.000Z') },
615
+ },
616
+ {
617
+ id: '3',
618
+ value: { text: 'TailwindCSS', time: new Date('2024-07-19T04:53:17.000Z') },
619
+ },
620
+ ];
621
+ ```
622
+
623
+ [Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
624
+ </details>
625
+
626
+ ##### Update state only and sync to URL manually
627
+
628
+ <details>
629
+ <Summary>Example</Summary>
630
+
631
+ ```typescript
632
+
633
+ const timer = React.useRef(0 as unknown as NodeJS.Timeout);
634
+ React.useEffect(() => {
635
+ clearTimeout(timer.current);
636
+ timer.current = setTimeout(() => {
637
+ // will compare state by content not by reference and fire update only for new values
638
+ setUrl(urlState);
639
+ }, 500);
640
+
641
+ return () => {
642
+ clearTimeout(timer.current);
643
+ };
644
+ }, [urlState, setUrl]);
645
+ ```
646
+
647
+ Syncing state `onBlur` will be more aligned with real world usage.
648
+
649
+ ```typescript
650
+ <input onBlur={() => updateUrl()} .../>
651
+ ```
652
+
653
+ </details>
654
+
632
655
  ## Other hooks and helpers
633
656
 
634
657
  ### `useUrlStateBase` hook for others routers
@@ -728,6 +751,12 @@ See [Contributing doc](CONTRIBUTING.md)
728
751
 
729
752
  ## [Changelog](CHANGELOG.md)
730
753
 
754
+ ## Mentions
755
+
756
+ - [This Week in React 209](https://thisweekinreact.com/newsletter/209)
757
+ - [JavaScript Weekly](https://javascriptweekly.com/issues/741)
758
+ - [This Week in React 240](https://thisweekinreact.com/newsletter/240)
759
+
731
760
  ## License
732
761
 
733
762
  This project is licensed under the [MIT license](LICENSE).
@@ -26,10 +26,11 @@ import { type JSONCompatible } from "../../utils";
26
26
  * setState({ name: 'test' });
27
27
  * setUrl({ name: 'test' }, { replace: true, scroll: true });
28
28
  * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
29
- * // reset state and url
29
+ * // RESET state and url
30
+ * setUrl((_curr, initialState) => initialState);
31
+ * // Or
30
32
  * reset();
31
33
  * reset({ replace: true });
32
- * // same as setState(form) with setUrl(form)
33
34
  * ```
34
35
  *
35
36
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState}
@@ -49,18 +50,20 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, p
49
50
  *
50
51
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updatestate}
51
52
  */
52
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
53
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
53
54
  /**
54
55
  * * Example:
55
56
  * ```ts
56
57
  * setUrl({ name: 'test' });
57
58
  * // or
58
- * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
59
+ * setUrl((curr) => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
60
+ * * Reset
61
+ * setUrl((_curr, initialState) => initialState, { replace: true, scroll: false } );
59
62
  * ```
60
63
  *
61
64
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updateurl}
62
65
  */
63
- setUrl: (value?: Partial<T> | ((currState: T) => T), options?: Options) => void;
66
+ setUrl: (value?: Partial<T> | ((currState: T, initialState: T) => T), options?: Options) => void;
64
67
  /**
65
68
  * * Example:
66
69
  * ```ts
@@ -1,4 +1,4 @@
1
- import { type NavigateOptions } from "react-router-dom";
1
+ import { type NavigateOptions } from "react-router";
2
2
  import { type JSONCompatible } from "../../utils";
3
3
  /**
4
4
  * React-router hook. Returns `urlState`, `setState`, and `setUrl` functions
@@ -23,10 +23,11 @@ import { type JSONCompatible } from "../../utils";
23
23
  * setUrl({ name: 'test' }, { replace: true });
24
24
  * // similar to React.useState
25
25
  * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
26
- * // reset state and url
26
+ * // RESET state and url
27
+ * setUrl((_curr, initialState) => initialState);
28
+ * // Or
27
29
  * reset();
28
30
  * reset({ replace: true });
29
- * // same as setState(form) with setUrl(form)
30
31
  * ```
31
32
  *
32
33
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState}
@@ -41,25 +42,25 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, p
41
42
  * ```ts
42
43
  * setState({ name: 'test' });
43
44
  * // or
44
- * setState(curr => ({ ...curr, name: 'test' }) );
45
+ * setState((curr, initial) => ({ ...curr, name: 'test' }) );
45
46
  * ```
46
47
  *
47
48
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
48
49
  */
49
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
50
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
50
51
  /**
51
52
  * * Example:
52
53
  * ```ts
53
54
  * setUrl({ name: 'test' });
54
55
  * // or
55
- * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
56
+ * setUrl((curr, initial) => ({ ...curr, name: 'test' }), { replace: true } );
56
57
  * // can pass optional React-Router `NavigateOptions`
57
58
  * setUrl(curr => ({ ...curr, name: 'test' }), { preventScrollReset: false } );
58
59
  * ```
59
60
  *
60
61
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
61
62
  */
62
- setUrl: (value?: Partial<T> | ((currState: T) => T), options?: Params) => void;
63
+ setUrl: (value?: Partial<T> | ((currState: T, initial: T) => T), options?: Params) => void;
63
64
  /**
64
65
  * * Example:
65
66
  * ```ts
@@ -1 +1 @@
1
- "use strict";var P=require("react");var c=require("react-router-dom");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function R(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...h,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=c.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[o]=c.useSearchParams();const d=c.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,o.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...o.entries()]),e))))},[o]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const h={replace:true,preventScrollReset:true};exports.useUrlState=R;
1
+ "use strict";var P=require("react");var o=require("react-router");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function R(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...h,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=o.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[c]=o.useSearchParams();const d=o.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,c.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...c.entries()]),e))))},[c]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const h={replace:true,preventScrollReset:true};exports.useUrlState=R;
@@ -1 +1 @@
1
- import s from"react";import{useNavigate as g,useSearchParams as k,useHref as P}from"react-router-dom";import{parseSPObj as h}from"../../parseSPObj.mjs";import{useUrlStateBase as C}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as H,filterUnknownParamsClient as R,assignValue as d,filterUnknownParams as j}from"../../utils.mjs";function w(t,a){const i=a?.useHistory;const o=s.useMemo(()=>({...y,replace:a?.replace,preventScrollReset:a?.preventScrollReset}),[]);const n=g();const m=s.useMemo(()=>i?H:{replace:(e,r)=>n(e,{...o,...r}),push:(e,r)=>n(e,{...o,...r})},[n]);const[c]=k();const f=P("/");const{state:S,updateState:l,updateUrl:u,getState:U,reset:p}=C(t,m,({parse:e})=>e(R(t,c.entries())),f);const b=s.useCallback((e,r)=>u(e,{...o,...r}),[u]);s.useEffect(()=>{l(d(t,j(t,h(Object.fromEntries([...c.entries()]),t))))},[c]);const v=s.useCallback(e=>{p({...o,...e})},[p]);return{setState:l,setUrl:b,urlState:S,reset:v,getState:U}}const y={replace:true,preventScrollReset:true};export{w as useUrlState};
1
+ import s from"react";import{useNavigate as g,useSearchParams as k,useHref as P}from"react-router";import{parseSPObj as h}from"../../parseSPObj.mjs";import{useUrlStateBase as C}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as H,filterUnknownParamsClient as R,assignValue as d,filterUnknownParams as j}from"../../utils.mjs";function w(t,a){const i=a?.useHistory;const o=s.useMemo(()=>({...y,replace:a?.replace,preventScrollReset:a?.preventScrollReset}),[]);const n=g();const m=s.useMemo(()=>i?H:{replace:(e,r)=>n(e,{...o,...r}),push:(e,r)=>n(e,{...o,...r})},[n]);const[c]=k();const f=P("/");const{state:S,updateState:l,updateUrl:u,getState:U,reset:p}=C(t,m,({parse:e})=>e(R(t,c.entries())),f);const b=s.useCallback((e,r)=>u(e,{...o,...r}),[u]);s.useEffect(()=>{l(d(t,j(t,h(Object.fromEntries([...c.entries()]),t))))},[c]);const v=s.useCallback(e=>{p({...o,...e})},[p]);return{setState:l,setUrl:b,urlState:S,reset:v,getState:U}}const y={replace:true,preventScrollReset:true};export{w as useUrlState};
@@ -0,0 +1 @@
1
+ export { useUrlState } from "./useUrlState";
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState/useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ import{useUrlState as t}from"./useUrlState/useUrlState.mjs";export{t as useUrlState};
@@ -0,0 +1 @@
1
+ export { useUrlState } from "./useUrlState";
@@ -0,0 +1 @@
1
+ "use strict";var e=require("./useUrlState.js");exports.useUrlState=e.useUrlState;
@@ -0,0 +1 @@
1
+ import{useUrlState as t}from"./useUrlState.mjs";export{t as useUrlState};
@@ -0,0 +1,83 @@
1
+ import { type NavigateOptions } from "react-router-dom";
2
+ import { type JSONCompatible } from "../../utils";
3
+ /**
4
+ * React-router hook. Returns `urlState`, `setState`, and `setUrl` functions
5
+ *
6
+ * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
7
+ * @param {Object} params - Object with other parameters
8
+ * @param {NavigateOptions} params.NavigateOptions See type from [`react-router-dom`](https://api.reactrouter.com/v7/interfaces/react_router.NavigateOptions.html)
9
+ * @param {boolean} params.replace replace URL or push, default `true`
10
+ * @param {boolean} params.useHistory use window.history for navigation, default `false`
11
+ * @param {boolean} params.preventScrollReset keep scroll position, default `true`
12
+ * @returns {Object} [result] State and callbacks
13
+ * @returns {Object} [result.state] - current state object
14
+ * @returns {Function} [result.setUrl] - function to update state and url
15
+ * @returns {Function} [result.setState] - function to update state only
16
+ * @returns {Function} [result.reset] - function to reset state and url to default
17
+ * * Example:
18
+ * ```ts
19
+ * export const form = { name: '', age: 0 };
20
+ * const { urlState, setState, setUrl } = useUrlState(form, { replace: false, preventScrollReset: false });
21
+ *
22
+ * setState({ name: 'test' });
23
+ * setUrl({ name: 'test' }, { replace: true });
24
+ * // similar to React.useState
25
+ * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
26
+ * // RESET state and url
27
+ * setUrl((_curr, initialState) => initialState);
28
+ * // Or
29
+ * reset();
30
+ * reset({ replace: true });
31
+ * ```
32
+ *
33
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState}
34
+ */
35
+ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, params?: Params): {
36
+ /**
37
+ * State object. Don't mutate directly, use `setState` or `setUrl`
38
+ */
39
+ urlState: T;
40
+ /**
41
+ * * Example:
42
+ * ```ts
43
+ * setState({ name: 'test' });
44
+ * // or
45
+ * setState((curr, initial) => ({ ...curr, name: 'test' }) );
46
+ * ```
47
+ *
48
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
49
+ */
50
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
51
+ /**
52
+ * * Example:
53
+ * ```ts
54
+ * setUrl({ name: 'test' });
55
+ * // or
56
+ * setUrl((curr, initial) => ({ ...curr, name: 'test' }), { replace: true } );
57
+ * // can pass optional React-Router `NavigateOptions`
58
+ * setUrl(curr => ({ ...curr, name: 'test' }), { preventScrollReset: false } );
59
+ * ```
60
+ *
61
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
62
+ */
63
+ setUrl: (value?: Partial<T> | ((currState: T, initial: T) => T), options?: Params) => void;
64
+ /**
65
+ * * Example:
66
+ * ```ts
67
+ * reset();
68
+ * // or
69
+ * reset({ replace: false, preventScrollReset: false })
70
+ * ```
71
+ *
72
+ * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#reset}
73
+ */
74
+ reset: (options?: NavigateOptions & {
75
+ [key: string]: unknown;
76
+ }) => void;
77
+ };
78
+ interface Params extends NavigateOptions {
79
+ useHistory?: boolean;
80
+ searchParams?: object;
81
+ replace?: boolean;
82
+ }
83
+ export {};
@@ -0,0 +1 @@
1
+ "use strict";var P=require("react");var c=require("react-router-dom");var _=require("../../parseSPObj.js");var g=require("../../useUrlStateBase/useUrlStateBase.js");var u=require("../../utils.js");function k(e){return e&&e.__esModule?e:{default:e}}var s=k(P);function R(e,l){const p=l?.useHistory;const a=s.default.useMemo(()=>({...h,replace:l?.replace,preventScrollReset:l?.preventScrollReset}),[]);const n=c.useNavigate();const v=s.default.useMemo(()=>p?u.routerHistory:{replace:(t,r)=>n(t,{...a,...r}),push:(t,r)=>n(t,{...a,...r})},[n]);const[o]=c.useSearchParams();const d=c.useHref("/");const{state:U,updateState:i,updateUrl:f,getState:m,reset:S}=g.useUrlStateBase(e,v,({parse:t})=>t(u.filterUnknownParamsClient(e,o.entries())),d);const b=s.default.useCallback((t,r)=>f(t,{...a,...r}),[f]);s.default.useEffect(()=>{i(u.assignValue(e,u.filterUnknownParams(e,_.parseSPObj(Object.fromEntries([...o.entries()]),e))))},[o]);const q=s.default.useCallback(t=>{S({...a,...t})},[S]);return{setState:i,setUrl:b,urlState:U,reset:q,getState:m}}const h={replace:true,preventScrollReset:true};exports.useUrlState=R;
@@ -0,0 +1 @@
1
+ import s from"react";import{useNavigate as g,useSearchParams as k,useHref as P}from"react-router-dom";import{parseSPObj as h}from"../../parseSPObj.mjs";import{useUrlStateBase as C}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as H,filterUnknownParamsClient as R,assignValue as d,filterUnknownParams as j}from"../../utils.mjs";function w(t,a){const i=a?.useHistory;const o=s.useMemo(()=>({...y,replace:a?.replace,preventScrollReset:a?.preventScrollReset}),[]);const n=g();const m=s.useMemo(()=>i?H:{replace:(e,r)=>n(e,{...o,...r}),push:(e,r)=>n(e,{...o,...r})},[n]);const[c]=k();const f=P("/");const{state:S,updateState:l,updateUrl:u,getState:U,reset:p}=C(t,m,({parse:e})=>e(R(t,c.entries())),f);const b=s.useCallback((e,r)=>u(e,{...o,...r}),[u]);s.useEffect(()=>{l(d(t,j(t,h(Object.fromEntries([...c.entries()]),t))))},[c]);const v=s.useCallback(e=>{p({...o,...e})},[p]);return{setState:l,setUrl:b,urlState:S,reset:v,getState:U}}const y={replace:true,preventScrollReset:true};export{w as useUrlState};
@@ -23,10 +23,11 @@ import { type JSONCompatible } from "../../utils";
23
23
  * setUrl({ name: 'test' }, { replace: true });
24
24
  * // similar to React.useState
25
25
  * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true });
26
- * // reset state and url
26
+ * // RESET state and url
27
+ * setUrl((_curr, initialState) => initialState);
28
+ * // Or
27
29
  * reset();
28
30
  * reset({ replace: true });
29
- * // same as setState(form) with setUrl(form)
30
31
  * ```
31
32
  *
32
33
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState}
@@ -41,25 +42,25 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, p
41
42
  * ```ts
42
43
  * setState({ name: 'test' });
43
44
  * // or
44
- * setState(curr => ({ ...curr, name: 'test' }) );
45
+ * setState((curr, initial) => ({ ...curr, name: 'test' }) );
45
46
  * ```
46
47
  *
47
48
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState#updatestate}
48
49
  */
49
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
50
+ setState: (value: Partial<T> | ((currState: T, initial: T) => T)) => void;
50
51
  /**
51
52
  * * Example:
52
53
  * ```ts
53
54
  * setUrl({ name: 'test' });
54
55
  * // or
55
- * setUrl(curr => ({ ...curr, name: 'test' }), { replace: true } );
56
+ * setUrl((curr, initial) => ({ ...curr, name: 'test' }), { replace: true } );
56
57
  * // can pass optional React-Router `NavigateOptions`
57
58
  * setUrl(curr => ({ ...curr, name: 'test' }), { preventScrollReset: false } );
58
59
  * ```
59
60
  *
60
61
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/remix/useUrlState#updateurl}
61
62
  */
62
- setUrl: (value?: Partial<T> | ((currState: T) => T), options?: Params) => void;
63
+ setUrl: (value?: Partial<T> | ((currState: T, initial: T) => T), options?: Params) => void;
63
64
  /**
64
65
  * * Example:
65
66
  * ```ts
@@ -13,12 +13,12 @@ import { type JSONCompatible } from "../utils";
13
13
  *
14
14
  * setState({ name: 'test' });
15
15
  * // OR
16
- * setState(curr => ({ ...curr, name: 'test' }))
16
+ * setState((curr, initialState) => ({ ...curr, name: 'test' }))
17
17
  * ```
18
18
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useSharedState}
19
19
  */
20
20
  export declare function useSharedState<T extends JSONCompatible>(defaultState: T, _getInitial?: () => T): {
21
21
  state: T;
22
22
  getState: () => T;
23
- setState: (value: Partial<T> | ((currState: T) => T)) => void;
23
+ setState: (value: Partial<T> | ((currState: T, defaultState: T) => T)) => void;
24
24
  };
@@ -1 +1 @@
1
- "use strict";var b=require("react");var u=require("../subscribers.js");var M=require("../useInsertionEffect.js");var i=require("../utils.js");function g(s){return s&&s.__esModule?s:{default:s}}var n=g(b);function v(s,a){const t=n.default.useRef(s);const[f,o]=n.default.useState(()=>{if(i.isSSR){return a?a?.():t.current}const e=u.stateMap.get(t.current);if(!e){const r=a?.()||t.current;u.stateMap.set(t.current,r);return r}else{return e}});const l=n.default.useCallback(e=>{const r=u.stateMap.get(t.current);const S=typeof e==="function";const c=S?e(r):{...r,...e};if(i.isEqual(r,c))return void 0;u.stateMap.set(t.current,c);u.subscribers.get(t.current).forEach(p=>p())},[]);M.useInsertionEffect(()=>{const e=()=>{o(u.stateMap.get(t.current)||t.current)};const r=u.subscribers.add(t.current,e);return()=>{r()}},[]);const d=n.default.useCallback(()=>u.stateMap.get(t.current)||t.current,[]);return{state:f,getState:d,setState:l}}exports.useSharedState=v;
1
+ "use strict";var b=require("react");var s=require("../subscribers.js");var M=require("../useInsertionEffect.js");var i=require("../utils.js");function g(u){return u&&u.__esModule?u:{default:u}}var n=g(b);function v(u,a){const t=n.default.useRef(u);const[o,f]=n.default.useState(()=>{if(i.isSSR){return a?a?.():t.current}const e=s.stateMap.get(t.current);if(!e){const r=a?.()||t.current;s.stateMap.set(t.current,r);return r}else{return e}});const l=n.default.useCallback(e=>{const r=s.stateMap.get(t.current);const S=typeof e==="function";const c=S?e(r,u):{...r,...e};if(i.isEqual(r,c))return void 0;s.stateMap.set(t.current,c);s.subscribers.get(t.current).forEach(p=>p())},[]);M.useInsertionEffect(()=>{const e=()=>{f(s.stateMap.get(t.current)||t.current)};const r=s.subscribers.add(t.current,e);return()=>{r()}},[]);const d=n.default.useCallback(()=>s.stateMap.get(t.current)||t.current,[]);return{state:o,getState:d,setState:l}}exports.useSharedState=v;
@@ -1 +1 @@
1
- import c from"react";import{stateMap as n,subscribers as o}from"../subscribers.mjs";import{useInsertionEffect as l}from"../useInsertionEffect.mjs";import{isSSR as b,isEqual as d}from"../utils.mjs";function E(a,u){const t=c.useRef(a);const[f,i]=c.useState(()=>{if(b){return u?u?.():t.current}const e=n.get(t.current);if(!e){const r=u?.()||t.current;n.set(t.current,r);return r}else{return e}});const m=c.useCallback(e=>{const r=n.get(t.current);const S=typeof e==="function";const s=S?e(r):{...r,...e};if(d(r,s))return void 0;n.set(t.current,s);o.get(t.current).forEach(g=>g())},[]);l(()=>{const e=()=>{i(n.get(t.current)||t.current)};const r=o.add(t.current,e);return()=>{r()}},[]);const p=c.useCallback(()=>n.get(t.current)||t.current,[]);return{state:f,getState:p,setState:m}}export{E as useSharedState};
1
+ import c from"react";import{stateMap as n,subscribers as a}from"../subscribers.mjs";import{useInsertionEffect as l}from"../useInsertionEffect.mjs";import{isSSR as b,isEqual as d}from"../utils.mjs";function E(o,s){const t=c.useRef(o);const[f,i]=c.useState(()=>{if(b){return s?s?.():t.current}const e=n.get(t.current);if(!e){const r=s?.()||t.current;n.set(t.current,r);return r}else{return e}});const m=c.useCallback(e=>{const r=n.get(t.current);const S=typeof e==="function";const u=S?e(r,o):{...r,...e};if(d(r,u))return void 0;n.set(t.current,u);a.get(t.current).forEach(g=>g())},[]);l(()=>{const e=()=>{i(n.get(t.current)||t.current)};const r=a.add(t.current,e);return()=>{r()}},[]);const p=c.useCallback(()=>n.get(t.current)||t.current,[]);return{state:f,getState:p,setState:m}}export{E as useSharedState};
@@ -23,7 +23,7 @@ import { type JSONCompatible, type Router } from "../utils";
23
23
  * );
24
24
  *
25
25
  * updateState({ name: 'John' });
26
- * updateState(curr => ({ ...curr, name: 'John' }));
26
+ * updateState((curr, initialState) => ({ ...curr, name: 'John' }));
27
27
  * updateUrl({ name: 'John' }, { replace: true });
28
28
  * updateUrl(curr => ({ ...curr, name: 'John' }), { replace: true });
29
29
  * reset()
@@ -36,8 +36,8 @@ import { type JSONCompatible, type Router } from "../utils";
36
36
  export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, getInitialState?: ({ parse, }: {
37
37
  parse: ReturnType<typeof useUrlEncode<T>>["parse"];
38
38
  }) => T, basename?: string): {
39
- updateState: (value: Partial<T> | ((currState: T) => T)) => void;
40
- updateUrl: (value?: Parameters<(value: Partial<T> | ((currState: T) => T)) => void>[0], options?: Options) => void;
39
+ updateState: (value: Partial<T> | ((currState: T, defaultState: T) => T)) => void;
40
+ updateUrl: (value?: Parameters<(value: Partial<T> | ((currState: T, defaultState: T) => T)) => void>[0], options?: Options) => void;
41
41
  state: T;
42
42
  reset: (options?: Options) => void;
43
43
  getState: () => T;
@@ -1 +1 @@
1
- "use strict";var y=require("react");var R=require("../constants/constants.js");var T=require("../useInsertionEffect.js");var _=require("../useSharedState/useSharedState.js");var b=require("../useUrlEncode/useUrlEncode.js");var B=require("../utils.js");function L(e){return e&&e.__esModule?e:{default:e}}var u=L(y);function P(e,t,i,l){const{parse:s,stringify:o}=b.useUrlEncode(e);const{state:f,getState:a,setState:r}=_.useSharedState(e,()=>i?.({parse:s})||e);const d=u.default.useRef(void 0);T.useInsertionEffect(()=>{const n=()=>{const p=s(B.filterUnknownParamsClient(e,I()));r(p)};window.addEventListener(q,n);return()=>{window.removeEventListener(q,n);clearTimeout(d.current)}},[r]);const w=u.default.useRef([]);const h=u.default.useCallback((n,p)=>{const S=typeof n==="function"?n(a()):{...a(),...n};const v=o(S,C(e));const U=M(window.location.pathname,l);const m=`${U}${v.length?"?":""}${v}${window.location.hash}`;const $=`${U}${window.location.search}${window.location.hash}`;if(m===$)return;r(S);const{replace:g,...k}=p||{};w.current.push([g?"replace":"push",m,k]);clearTimeout(d.current);const c=w.current.filter(Boolean).at(-1);if(c){queueMicrotask(()=>{d.current=setTimeout(()=>{w.current=[];t[c[0]](c[1],c[2])},R.TIMEOUT)})}},[t,o,a]);const E=u.default.useCallback(n=>{r(e);h(e,n)},[h,r]);return{updateState:r,updateUrl:h,state:f,reset:E,getState:a}}function C(e){const t=Object.keys(e);const i=window.location.search;const l=new URLSearchParams(i);const s=new URLSearchParams;for(const[o,f]of l){!t.includes(o)&&s.set(o,f)}return s}const q="popstate";function I(){return typeof window!=="undefined"&&window.location.search||""}function M(e,t){if(!t||t==="/"){return e}return e.slice(t.length)}exports.useUrlStateBase=P;
1
+ "use strict";var y=require("react");var R=require("../constants/constants.js");var T=require("../useInsertionEffect.js");var _=require("../useSharedState/useSharedState.js");var B=require("../useUrlEncode/useUrlEncode.js");var L=require("../utils.js");function P(e){return e&&e.__esModule?e:{default:e}}var u=P(y);function b(e,t,i,l){const{parse:s,stringify:o}=B.useUrlEncode(e);const{state:f,getState:a,setState:r}=_.useSharedState(e,()=>i?.({parse:s})||e);const d=u.default.useRef(void 0);T.useInsertionEffect(()=>{const n=()=>{const p=s(L.filterUnknownParamsClient(e,I()));r(p)};window.addEventListener(q,n);return()=>{window.removeEventListener(q,n);clearTimeout(d.current)}},[r]);const w=u.default.useRef([]);const h=u.default.useCallback((n,p)=>{const S=typeof n==="function"?n(a(),e):{...a(),...n};const v=o(S,C(e));const m=M(window.location.pathname,l);const U=`${m}${v.length?"?":""}${v}${window.location.hash}`;const $=`${m}${window.location.search}${window.location.hash}`;if(U===$)return;r(S);const{replace:g,...k}=p||{};w.current.push([g?"replace":"push",U,k]);clearTimeout(d.current);const c=w.current.filter(Boolean).at(-1);if(c){queueMicrotask(()=>{d.current=setTimeout(()=>{w.current=[];t[c[0]](c[1],c[2])},R.TIMEOUT)})}},[t,o,a]);const E=u.default.useCallback(n=>{r(e);h(e,n)},[h,r]);return{updateState:r,updateUrl:h,state:f,reset:E,getState:a}}function C(e){const t=Object.keys(e);const i=window.location.search;const l=new URLSearchParams(i);const s=new URLSearchParams;for(const[o,f]of l){!t.includes(o)&&s.set(o,f)}return s}const q="popstate";function I(){return typeof window!=="undefined"&&window.location.search||""}function M(e,t){if(!t||t==="/"){return e}return e.slice(t.length)}exports.useUrlStateBase=b;
@@ -1 +1 @@
1
- import i from"react";import{TIMEOUT as T}from"../constants/constants.mjs";import{useInsertionEffect as b}from"../useInsertionEffect.mjs";import{useSharedState as v}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as L}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as P}from"../utils.mjs";function B(t,e,u,f){const{parse:r,stringify:s}=L(t);const{state:l,getState:c,setState:o}=v(t,()=>u?.({parse:r})||t);const w=i.useRef(void 0);b(()=>{const n=()=>{const d=r(P(t,q()));o(d)};window.addEventListener(g,n);return()=>{window.removeEventListener(g,n);clearTimeout(w.current)}},[o]);const m=i.useRef([]);const p=i.useCallback((n,d)=>{const h=typeof n==="function"?n(c()):{...c(),...n};const S=s(h,C(t));const $=I(window.location.pathname,f);const U=`${$}${S.length?"?":""}${S}${window.location.hash}`;const E=`${$}${window.location.search}${window.location.hash}`;if(U===E)return;o(h);const{replace:y,...R}=d||{};m.current.push([y?"replace":"push",U,R]);clearTimeout(w.current);const a=m.current.filter(Boolean).at(-1);if(a){queueMicrotask(()=>{w.current=setTimeout(()=>{m.current=[];e[a[0]](a[1],a[2])},T)})}},[e,s,c]);const k=i.useCallback(n=>{o(t);p(t,n)},[p,o]);return{updateState:o,updateUrl:p,state:l,reset:k,getState:c}}function C(t){const e=Object.keys(t);const u=window.location.search;const f=new URLSearchParams(u);const r=new URLSearchParams;for(const[s,l]of f){!e.includes(s)&&r.set(s,l)}return r}const g="popstate";function q(){return typeof window!=="undefined"&&window.location.search||""}function I(t,e){if(!e||e==="/"){return t}return t.slice(e.length)}export{B as useUrlStateBase};
1
+ import i from"react";import{TIMEOUT as T}from"../constants/constants.mjs";import{useInsertionEffect as v}from"../useInsertionEffect.mjs";import{useSharedState as L}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as P}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as b}from"../utils.mjs";function B(t,e,u,f){const{parse:r,stringify:s}=P(t);const{state:l,getState:c,setState:o}=L(t,()=>u?.({parse:r})||t);const m=i.useRef(void 0);v(()=>{const n=()=>{const d=r(b(t,O()));o(d)};window.addEventListener(g,n);return()=>{window.removeEventListener(g,n);clearTimeout(m.current)}},[o]);const w=i.useRef([]);const p=i.useCallback((n,d)=>{const h=typeof n==="function"?n(c(),t):{...c(),...n};const S=s(h,C(t));const $=q(window.location.pathname,f);const U=`${$}${S.length?"?":""}${S}${window.location.hash}`;const E=`${$}${window.location.search}${window.location.hash}`;if(U===E)return;o(h);const{replace:y,...R}=d||{};w.current.push([y?"replace":"push",U,R]);clearTimeout(m.current);const a=w.current.filter(Boolean).at(-1);if(a){queueMicrotask(()=>{m.current=setTimeout(()=>{w.current=[];e[a[0]](a[1],a[2])},T)})}},[e,s,c]);const k=i.useCallback(n=>{o(t);p(t,n)},[p,o]);return{updateState:o,updateUrl:p,state:l,reset:k,getState:c}}function C(t){const e=Object.keys(t);const u=window.location.search;const f=new URLSearchParams(u);const r=new URLSearchParams;for(const[s,l]of f){!e.includes(s)&&r.set(s,l)}return r}const g="popstate";function O(){return typeof window!=="undefined"&&window.location.search||""}function q(t,e){if(!e||e==="/"){return t}return t.slice(e.length)}export{B as useUrlStateBase};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "Store state in URL as in object, types and structure are preserved, with TS validation. Same API as React.useState, wthout any hasssle or boilerplate. Next.js@14-15, react-router@6-7, and remix@2.",
5
5
  "homepage": "https://state-in-url.dev",
6
6
  "repository": {
@@ -31,6 +31,7 @@
31
31
  "packages/example-nextjs15",
32
32
  "packages/example-react",
33
33
  "packages/example-react-router6",
34
+ "packages/example-react-router7",
34
35
  "packages/example-remix2"
35
36
  ],
36
37
  "files": [
@@ -49,6 +50,11 @@
49
50
  "default": "./dist/next/index.mjs",
50
51
  "require": "./dist/next/index.js"
51
52
  },
53
+ "./react-router6": {
54
+ "types": "./dist/react-router6/index.d.ts",
55
+ "default": "./dist/react-router6/index.mjs",
56
+ "require": "./dist/react-router6/index.js"
57
+ },
52
58
  "./react-router": {
53
59
  "types": "./dist/react-router/index.d.ts",
54
60
  "default": "./dist/react-router/index.mjs",
@@ -118,7 +124,7 @@
118
124
  "kill-vite": "wireit",
119
125
  "kill": "wireit",
120
126
  "rmdist": "rm -rf dist",
121
- "cleanup": "rm -rf dist .wireit node_modules test-results playwright-report coverage-reports nyc_coverage packages/example-nextjs14/node_modules packages/example-nextjs14/.next packages/example-nextjs15/node_modules packages/example-nextjs15/.next packages/example-react/node_modules packages/example-react-router6/node_modules packages/example-remix2/node_modules",
127
+ "cleanup": "rm -rf dist .wireit node_modules test-results playwright-report coverage-reports nyc_coverage packages/example-nextjs14/node_modules packages/example-nextjs14/.next packages/example-nextjs15/node_modules packages/example-nextjs15/.next packages/example-react/node_modules packages/example-react-router6/node_modules packages/example-react-router7/node_modules packages/example-remix2/node_modules",
122
128
  "reinstall": "npm run cleanup && npm install",
123
129
  "setup": "playwright install --with-deps",
124
130
  "prepack": "npm run build && cp package.json dist/package.json && npm run build:demo",
@@ -172,6 +178,7 @@
172
178
  "dev:react",
173
179
  "dev:remix2",
174
180
  "dev:router6",
181
+ "dev:router7",
175
182
  "dev:nextjs15"
176
183
  ],
177
184
  "service": {
@@ -196,6 +203,10 @@
196
203
  "command": "cd packages/example-react-router6 && npm run dev",
197
204
  "service": true
198
205
  },
206
+ "dev:router7": {
207
+ "command": "cd packages/example-react-router7 && npm run dev",
208
+ "service": true
209
+ },
199
210
  "dev:remix2": {
200
211
  "command": "cd packages/example-remix2 && PORT=3003 npm run dev",
201
212
  "service": true
@@ -220,6 +231,12 @@
220
231
  "build"
221
232
  ]
222
233
  },
234
+ "build:router7": {
235
+ "command": "cd packages/example-react-router7 && npm run build",
236
+ "dependencies": [
237
+ "build"
238
+ ]
239
+ },
223
240
  "build:remix2": {
224
241
  "command": "cd packages/example-remix2 && npm run build",
225
242
  "dependencies": [
@@ -271,6 +288,17 @@
271
288
  "command": "npx vite preview --port 5181 packages/example-react-router6",
272
289
  "service": true
273
290
  },
291
+ "start:router7": {
292
+ "command": "npx vite preview --port 5183 packages/example-react-router7",
293
+ "dependencies": [
294
+ "build:router7"
295
+ ],
296
+ "service": true
297
+ },
298
+ "start:router7:ci": {
299
+ "command": "npx vite preview --port 5183 packages/example-react-router7",
300
+ "service": true
301
+ },
274
302
  "start:remix2": {
275
303
  "command": "cd packages/example-remix2 && npm run start",
276
304
  "dependencies": [
@@ -299,6 +327,7 @@
299
327
  "start:demo",
300
328
  "start:react",
301
329
  "start:router6",
330
+ "start:router7",
302
331
  "start:remix2"
303
332
  ],
304
333
  "service": true
@@ -309,6 +338,7 @@
309
338
  "start:demo:ci",
310
339
  "start:react:ci",
311
340
  "start:router6:ci",
341
+ "start:router7:ci",
312
342
  "start:remix2:ci"
313
343
  ],
314
344
  "service": true
@@ -324,6 +354,7 @@
324
354
  "build:demo:tests",
325
355
  "build:next15",
326
356
  "build:router6",
357
+ "build:router7",
327
358
  "build:remix2"
328
359
  ]
329
360
  },
@@ -488,5 +519,8 @@
488
519
  "decode url",
489
520
  "fast"
490
521
  ],
491
- "packageManager": "npm@10.8.2"
522
+ "packageManager": "npm@10.8.2",
523
+ "dependencies": {
524
+ "react-router": "^7.7.1"
525
+ }
492
526
  }