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 +209 -180
- package/dist/next/useUrlState/useUrlState.d.ts +8 -5
- package/dist/react-router/useUrlState/useUrlState.d.ts +8 -7
- package/dist/react-router/useUrlState/useUrlState.js +1 -1
- package/dist/react-router/useUrlState/useUrlState.mjs +1 -1
- package/dist/react-router6/index.d.ts +1 -0
- package/dist/react-router6/index.js +1 -0
- package/dist/react-router6/index.mjs +1 -0
- package/dist/react-router6/useUrlState/index.d.ts +1 -0
- package/dist/react-router6/useUrlState/index.js +1 -0
- package/dist/react-router6/useUrlState/index.mjs +1 -0
- package/dist/react-router6/useUrlState/useUrlState.d.ts +83 -0
- package/dist/react-router6/useUrlState/useUrlState.js +1 -0
- package/dist/react-router6/useUrlState/useUrlState.mjs +1 -0
- package/dist/remix/useUrlState/useUrlState.d.ts +7 -6
- package/dist/useSharedState/useSharedState.d.ts +2 -2
- package/dist/useSharedState/useSharedState.js +1 -1
- package/dist/useSharedState/useSharedState.mjs +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.d.ts +3 -3
- package/dist/useUrlStateBase/useUrlStateBase.js +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/package.json +37 -3
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
|
-
- [
|
|
103
|
-
- [
|
|
104
|
-
- -
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
- [
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
- [
|
|
114
|
-
-
|
|
115
|
-
- [
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
- -
|
|
121
|
-
-
|
|
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
|
|
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={
|
|
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
|
-
* //
|
|
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
|
|
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
|
-
* //
|
|
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
|
|
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
|
|
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
|
-
* //
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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
|
}
|