state-in-url 4.1.0 → 4.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.CN.md +1 -1
- package/README.KO.md +1 -1
- package/README.md +104 -56
- package/dist/constants/constants.d.ts +1 -0
- package/dist/constants/constants.mjs +1 -1
- package/dist/constants/index.mjs +1 -1
- package/dist/encoder/encoder.d.ts +8 -8
- package/dist/encoder/encoder.mjs +1 -1
- package/dist/next/useUrlState/useUrlState.d.ts +1 -1
- package/dist/next/useUrlState/useUrlState.mjs +1 -1
- package/dist/parseSPObj.mjs +1 -1
- package/dist/react-router/useUrlState/useUrlState.d.ts +1 -1
- package/dist/testUtils.d.ts +1 -0
- package/dist/useSharedState/useSharedState.mjs +1 -1
- package/dist/useUrlEncode/useUrlEncode.d.ts +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.d.ts +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/dist/utils.d.ts +5 -1
- package/dist/utils.mjs +1 -1
- package/package.json +11 -5
package/README.CN.md
CHANGED
|
@@ -89,7 +89,7 @@ pnpm add state-in-url
|
|
|
89
89
|
|
|
90
90
|
## Next.js的useUrlState钩子
|
|
91
91
|
|
|
92
|
-
[文档](packages/urlstate/next/useUrlState
|
|
92
|
+
[文档](packages/urlstate/next/useUrlState)
|
|
93
93
|
|
|
94
94
|
`useUrlState`是一个为Next.js应用程序设计的自定义React钩子,使客户端组件之间的通信变得简单。它允许你共享任何复杂的状态并将其与URL搜索参数同步,提供了一种在页面重新加载之间持久化状态并通过URL共享应用程序状态的方法。
|
|
95
95
|
|
package/README.KO.md
CHANGED
|
@@ -96,7 +96,7 @@ pnpm add state-in-url
|
|
|
96
96
|
|
|
97
97
|
## Next.js용 useUrlState 훅
|
|
98
98
|
|
|
99
|
-
[문서](packages/urlstate/next/useUrlState
|
|
99
|
+
[문서](packages/urlstate/next/useUrlState)
|
|
100
100
|
|
|
101
101
|
`useUrlState`는 Next.js 애플리케이션을 위한 사용자 정의 React 훅으로, 클라이언트 컴포넌트 간의 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주어, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유할 수 있습니다.
|
|
102
102
|
|
package/README.md
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
|
-
<img src="/
|
|
5
|
-
|
|
6
|
-
<div><h2> Store any complex state in query parameters, URL synchronization (aka deep links) made easy. Share state between any unrelated components. With support of static types validation and server side rendering.</h2></div>
|
|
7
|
-
|
|
4
|
+
<img src="/packages/example-nextjs14/public/Logo_symbol.png" alt="state-in-url logo" width="120px" />
|
|
8
5
|
|
|
6
|
+
# State in url
|
|
9
7
|
</div>
|
|
10
8
|
|
|
11
|
-
<hr />
|
|
12
|
-
|
|
13
9
|
<div align="center">
|
|
14
|
-
<a href="https://github.com/asmyshlyaev177/state-in-url/blob/master/Limits.md" target="_blank">URI size limitation, <b>up to 12KB</b> is safe</a>
|
|
15
10
|
</div>
|
|
16
11
|
|
|
17
12
|
<div align="center">
|
|
18
|
-
<hr />
|
|
19
13
|
|
|
20
14
|
[](https://www.npmjs.com/package/state-in-url)
|
|
21
|
-

|
|
22
16
|
[](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
17
|
+
|
|
18
|
+

|
|
23
19
|
[](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
|
|
24
20
|
[](https://github.com/asmyshlyaev177/state-in-url/)
|
|
25
21
|
[]([https://github.com/semantic-release/semantic-release](https://github.com/asmyshlyaev177/state-in-url))
|
|
26
|
-
|
|
22
|
+
|
|
27
23
|
[](https://scorecard.dev/viewer/?uri=github.com/asmyshlyaev177/state-in-url)
|
|
28
24
|
[](https://www.bestpractices.dev/projects/9679)
|
|
29
25
|
<!-- or by embedding this in your HTML:
|
|
@@ -40,6 +36,8 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
|
|
|
40
36
|
<a href="https://state-in-url.dev" target="_blank">DEMO</a> |
|
|
41
37
|
<a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a>
|
|
42
38
|
|
|
39
|
+
<a href="https://github.com/asmyshlyaev177/state-in-url/blob/master/Limits.md" target="_blank">URI size limitation, <b>up to 12KB</b> is safe</a>
|
|
40
|
+
|
|
43
41
|
<hr />
|
|
44
42
|
|
|
45
43
|
Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">follow me</a> to support the project!
|
|
@@ -47,18 +45,18 @@ Add a ⭐️ and <a href="https://github.com/asmyshlyaev177" target="_blank">fol
|
|
|
47
45
|
Will appreciate you feedback/opinion on [discussions](https://github.com/asmyshlyaev177/state-in-url/discussions/1)
|
|
48
46
|
|
|
49
47
|
Share if it useful for you.
|
|
50
|
-
[FB](https://www.facebook.com/sharer.php?u=https://github.com/asmyshlyaev177/state-in-url)
|
|
51
|
-
[LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://github.com/asmyshlyaev177/state-in-url)
|
|
52
48
|
[X.com](https://twitter.com/intent/tweet?&url=https://github.com/asmyshlyaev177/state-in-url)
|
|
49
|
+
[LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://github.com/asmyshlyaev177/state-in-url)
|
|
50
|
+
[FB](https://www.facebook.com/sharer.php?u=https://github.com/asmyshlyaev177/state-in-url)
|
|
53
51
|
[VK](http://vk.com/share.php?url=https://github.com/asmyshlyaev177/state-in-url)
|
|
54
52
|
|
|
55
53
|
<hr />
|
|
56
54
|
|
|
57
55
|
</div>
|
|
58
56
|
|
|
59
|
-
|
|
57
|
+
## Why use `state-in-url`?
|
|
60
58
|
|
|
61
|
-
Store any user state in query parameters; imagine JSON in a browser URL
|
|
59
|
+
Store any user state in query parameters; imagine JSON in a browser URL. All of it with keeping types and structure of data, e.g. numbers will be decoded as numbers not strings, dates as dates, etc, objects and arrays supported.
|
|
62
60
|
Dead simple, fast, and with static Typescript validation. Deep links, aka URL synchronization, made easy.
|
|
63
61
|
|
|
64
62
|
Contains `useUrlState` hook for Next.js and react-router, and helpers for anything else on JS.
|
|
@@ -69,27 +67,28 @@ This library does all mundane stuff for you.
|
|
|
69
67
|
|
|
70
68
|
This library is a good alternative for NUQS.
|
|
71
69
|
|
|
72
|
-
|
|
70
|
+
### Use cases
|
|
73
71
|
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
72
|
+
- Store unsaved user forms or page filters in URL
|
|
73
|
+
- Sycn URL with React state
|
|
74
|
+
- Just sync data between unrelated client components without touching URI
|
|
75
|
+
- Shareable URLs with application state (Deep linking, URL state synchronization)
|
|
76
|
+
- Easy state persistence across page reloads
|
|
78
77
|
|
|
79
|
-
|
|
78
|
+
### Features
|
|
80
79
|
|
|
81
80
|
- 🧩 **Simple**: No providers, reducers, boilerplate or new concepts, API similar to `React.useState`
|
|
82
|
-
- 📘 **Typescript validation**: State is just an object, automatic static validation in IDE/tests according to Typescript definition
|
|
81
|
+
- 📘 **Typescript validation/autocomplete**: State is just an object, automatic static validation in IDE/tests according to Typescript definition
|
|
83
82
|
- ✨ **Complex data**: Nested objects, dates and arrays, works same as JSON, but in URL
|
|
84
|
-
- 🔵 **Server Side Rendering**: Can use it in Server Components, Next.js 14 and 15 are supported
|
|
85
83
|
- ☂ **Default values**: Giving you default values if parameter not in url
|
|
86
84
|
- ⌨ **Organized**: All possible values defined at start, protect you from getting non existing key
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
85
|
+
- **Fast**: Minimal rerenders, less than [1ms](https://github.com/asmyshlyaev177/state-in-url/blob/87c8c7c995c5cd7d9e7aa039c30bfe64b24abe4b/packages/urlstate/encoder/encoder.test.ts#L185) to encode and decode big object
|
|
86
|
+
- **Server Side Rendering**: Can use it in Server Components, Next.js 14 and 15 are supported
|
|
87
|
+
- **Lightweight**: Zero dependencies, library less than 2KB
|
|
88
|
+
- **DX**: Good developer experience, documentation, JSDoc comments, and examples
|
|
89
|
+
- **Framework Flexibility**: Hooks for `Next.js` and `react-router`, helpers to use it with other frameworks or pure JS
|
|
90
|
+
- **Well tested**: Unit tests and Playwright tests for Chrome/Firefox/Safari
|
|
91
|
+
- **Permissive license**: MIT
|
|
93
92
|
|
|
94
93
|
## Table of content
|
|
95
94
|
|
|
@@ -136,26 +135,26 @@ Possibly need to set `"module": "ES2022"`, or `"module": "ESNext"`
|
|
|
136
135
|
Main hook that takes initial state as parameter and returns state object, callback to update url, and callback to update only state.
|
|
137
136
|
All components that use the same `state` object are automatically synchronized.
|
|
138
137
|
|
|
139
|
-
|
|
138
|
+
### useUrlState hook for Next.js
|
|
140
139
|
|
|
141
|
-
[Full API Docs](packages/urlstate/next/useUrlState
|
|
140
|
+
[Full API Docs](packages/urlstate/next/useUrlState)
|
|
142
141
|
|
|
143
142
|
[React-Router example](#useurlstate-hook-for-react-router)
|
|
144
143
|
|
|
145
|
-
|
|
144
|
+
#### Usage examples
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
##### Basic
|
|
148
147
|
|
|
149
148
|
1. Define state shape with default values
|
|
150
149
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
```typescript
|
|
151
|
+
// userState.ts
|
|
152
|
+
// Only parameters with value different from default will go to the url.
|
|
153
|
+
export const userState: UserState = { name: '', age: 0 }
|
|
155
154
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
155
|
+
// use `Type` not `Interface`!
|
|
156
|
+
type UserState = { name: string, age: number }
|
|
157
|
+
```
|
|
159
158
|
|
|
160
159
|
2. Import it and use
|
|
161
160
|
|
|
@@ -196,7 +195,56 @@ function MyComponent() {
|
|
|
196
195
|
}
|
|
197
196
|
```
|
|
198
197
|
|
|
199
|
-
|
|
198
|
+
##### Custom hook to work with slice of state conveniently
|
|
199
|
+
<details>
|
|
200
|
+
<Summary>Example</Summary>
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
'use client';
|
|
204
|
+
|
|
205
|
+
import React from 'react';
|
|
206
|
+
import { useUrlState } from 'state-in-url/next';
|
|
207
|
+
|
|
208
|
+
const form: Form = {
|
|
209
|
+
name: '',
|
|
210
|
+
age: undefined,
|
|
211
|
+
agree_to_terms: false,
|
|
212
|
+
tags: [],
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
type Form = {
|
|
216
|
+
name: string;
|
|
217
|
+
age?: number;
|
|
218
|
+
agree_to_terms: boolean;
|
|
219
|
+
tags: {id: string; value: {text: string; time: Date } }[];
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const useFormState = ({ searchParams }: { searchParams?: object }) => {
|
|
223
|
+
const { urlState, setUrl: setUrlBase, reset } = useUrlState(form, {
|
|
224
|
+
searchParams,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// first navigation will push new history entry
|
|
228
|
+
// all following will just replace that entry
|
|
229
|
+
// this way will have history with only 2 entries - ['/url', '/url?key=param']
|
|
230
|
+
|
|
231
|
+
const replace = React.useRef(false);
|
|
232
|
+
const setUrl = React.useCallback((
|
|
233
|
+
state: Parameters<typeof setUrlBase>[0],
|
|
234
|
+
opts?: Parameters<typeof setUrlBase>[1]
|
|
235
|
+
) => {
|
|
236
|
+
setUrlBase(state, { replace: replace.current, ...opts });
|
|
237
|
+
replace.current = true;
|
|
238
|
+
}, [setUrlBase]);
|
|
239
|
+
|
|
240
|
+
return { urlState, setUrl, resetUrl: reset };
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
</details>
|
|
244
|
+
|
|
245
|
+
<hr />
|
|
246
|
+
|
|
247
|
+
##### With complex state shape
|
|
200
248
|
|
|
201
249
|
<details>
|
|
202
250
|
<Summary>Example</Summary>
|
|
@@ -276,7 +324,7 @@ const tags = [
|
|
|
276
324
|
[Demo page example code](https://github.com/asmyshlyaev177/state-in-url/blob/master/packages/example-nextjs14/src/app/Form.tsx)
|
|
277
325
|
</details>
|
|
278
326
|
|
|
279
|
-
|
|
327
|
+
##### Update state only and sync to URL manually
|
|
280
328
|
|
|
281
329
|
<details>
|
|
282
330
|
<Summary>Example</Summary>
|
|
@@ -305,7 +353,7 @@ Syncing state `onBlur` will be more aligned with real world usage.
|
|
|
305
353
|
|
|
306
354
|
</details>
|
|
307
355
|
|
|
308
|
-
|
|
356
|
+
##### With server side rendering
|
|
309
357
|
|
|
310
358
|
<details>
|
|
311
359
|
<Summary>Example</Summary>
|
|
@@ -330,7 +378,7 @@ const Form = ({ searchParams }: { searchParams: object }) => {
|
|
|
330
378
|
|
|
331
379
|
</details>
|
|
332
380
|
|
|
333
|
-
|
|
381
|
+
##### Using hook in `layout` component
|
|
334
382
|
|
|
335
383
|
<details>
|
|
336
384
|
<Summary>Example</Summary>
|
|
@@ -381,7 +429,7 @@ export default async function Layout({
|
|
|
381
429
|
|
|
382
430
|
</details>
|
|
383
431
|
|
|
384
|
-
|
|
432
|
+
##### With arbitrary state shape (not recommended)
|
|
385
433
|
|
|
386
434
|
<details>
|
|
387
435
|
<Summary>Example</Summary>
|
|
@@ -399,13 +447,13 @@ function SettingsComponent() {
|
|
|
399
447
|
|
|
400
448
|
</details>
|
|
401
449
|
|
|
402
|
-
|
|
450
|
+
### useUrlState hook for React-Router
|
|
403
451
|
|
|
404
452
|
API is same as for Next.js version, except can pass options from [NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) type.
|
|
405
453
|
|
|
406
|
-
[API Docs](packages/urlstate/react-router/useUrlState
|
|
454
|
+
[API Docs](packages/urlstate/react-router/useUrlState)
|
|
407
455
|
|
|
408
|
-
|
|
456
|
+
#### Example
|
|
409
457
|
|
|
410
458
|
```typescript
|
|
411
459
|
export const form: Form = {
|
|
@@ -486,15 +534,15 @@ const tags = [
|
|
|
486
534
|
|
|
487
535
|
[Example code](packages/example-react-router6/src/Form-for-test.tsx)
|
|
488
536
|
|
|
489
|
-
|
|
537
|
+
## Other hooks and helpers
|
|
490
538
|
|
|
491
|
-
|
|
539
|
+
### `useUrlStateBase` hook for others routers
|
|
492
540
|
|
|
493
541
|
Hooks to create your own `useUrlState` hooks with other routers, e.g. react-router or tanstack router.
|
|
494
542
|
|
|
495
543
|
[API Docs](packages/urlstate/useUrlStateBase)
|
|
496
544
|
|
|
497
|
-
|
|
545
|
+
### `useSharedState` hook for React.js
|
|
498
546
|
|
|
499
547
|
Hook to share state between any React components, tested with Next.js and Vite.
|
|
500
548
|
|
|
@@ -511,19 +559,19 @@ function SettingsComponent() {
|
|
|
511
559
|
|
|
512
560
|
[API Docs](packages/urlstate/useSharedState/README.md)
|
|
513
561
|
|
|
514
|
-
|
|
562
|
+
### `useUrlEncode` hook for React.js
|
|
515
563
|
|
|
516
564
|
[API Docs](packages/urlstate/useUrlEncode/README.md)
|
|
517
565
|
|
|
518
|
-
|
|
566
|
+
### `encodeState` and `decodeState` helpers
|
|
519
567
|
|
|
520
568
|
[API Docs](packages/urlstate/encodeState/README.md)
|
|
521
569
|
|
|
522
|
-
|
|
570
|
+
### `encode` and `decode` helpers
|
|
523
571
|
|
|
524
572
|
[API Docs](packages/urlstate/encoder/README.md)
|
|
525
573
|
|
|
526
|
-
|
|
574
|
+
## Best Practices
|
|
527
575
|
|
|
528
576
|
- Define your state shape as a constant
|
|
529
577
|
- Use TypeScript for enhanced type safety and autocomplete
|
|
@@ -557,9 +605,9 @@ export const useUserState = () => {
|
|
|
557
605
|
2. Vercel servers limit size of headers (query string and other stuff) to **14KB**, so keep your URL state under ~5000 words. <https://vercel.com/docs/errors/URL_TOO_LONG>
|
|
558
606
|
3. Tested with `next.js` 14/15 with app router, no plans to support pages.
|
|
559
607
|
|
|
560
|
-
|
|
608
|
+
## Other
|
|
561
609
|
|
|
562
|
-
|
|
610
|
+
### Contribute and/or run locally
|
|
563
611
|
|
|
564
612
|
See [Contributing doc](CONTRIBUTING.md)
|
|
565
613
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{isSafari as e}from"../utils.mjs";const d={date:"⏲",undefined:"∙undefined"},n=e?330:70;export{d as SYMBOLS,n as TIMEOUT};
|
package/dist/constants/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{SYMBOLS}from"./constants.mjs";
|
|
1
|
+
export{SYMBOLS,TIMEOUT}from"./constants.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSONCompatible } from "../utils";
|
|
1
|
+
import { type JSONCompatible, type Simple } from "../utils";
|
|
2
2
|
/**
|
|
3
3
|
* Encode any JSON serializable value to URL friendly string
|
|
4
4
|
*
|
|
@@ -8,7 +8,7 @@ import { type JSONCompatible } from "../utils";
|
|
|
8
8
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#encode}
|
|
9
9
|
*/
|
|
10
10
|
export declare function encode(payload: unknown): string;
|
|
11
|
-
export
|
|
11
|
+
export declare const encodePrimitive: (payload: Simple) => Simple;
|
|
12
12
|
/**
|
|
13
13
|
* Decode value part of URLSearchParams back to JS value
|
|
14
14
|
*
|
|
@@ -18,17 +18,17 @@ export type Primitive = Exclude<ReturnType<typeof decodePrimitive>, typeof error
|
|
|
18
18
|
*
|
|
19
19
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/encoder#decode}
|
|
20
20
|
*/
|
|
21
|
-
export declare function decode<T>(payload: string, fallback?: T):
|
|
22
|
-
export declare const decodePrimitive: (str: string) => Date | typeof errorSym | undefined;
|
|
23
|
-
export declare const errorSym: unique symbol;
|
|
24
|
-
export declare const reviver: (_key: string, value: unknown) => unknown;
|
|
21
|
+
export declare function decode<T>(payload: string, fallback?: T): T;
|
|
25
22
|
/**
|
|
26
23
|
* Parses a JSON string into a TypeScript object.
|
|
27
24
|
*
|
|
28
25
|
* @param {string} jsonString - The JSON string to parse.
|
|
29
26
|
* @param {T} [fallbackValue] - The fallback value to use if parsing fails.
|
|
30
|
-
* @return {T |
|
|
27
|
+
* @return {T | CustomDecoded | undefined} - The parsed object or a primitive value, or undefined if parsing fails.
|
|
31
28
|
*
|
|
32
29
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
33
30
|
*/
|
|
34
|
-
export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T |
|
|
31
|
+
export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T | CustomDecoded | undefined;
|
|
32
|
+
export declare function reviver(_key: string, value: unknown): unknown;
|
|
33
|
+
export declare const decodePrimitive: (str: string) => string | Date | undefined;
|
|
34
|
+
export type CustomDecoded = ReturnType<typeof decodePrimitive>;
|
package/dist/encoder/encoder.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as e}from"../utils.mjs";function n(n){if(
|
|
1
|
+
import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as e}from"../utils.mjs";function n(t){return f(t)?t:JSON.stringify(t,r).replace(/'/g,"%27").replace(/"/g,"'")}function r(t,n){const a=e(n);if("object"!==a&&"array"!==a)return c(n);if("object"===a){const t=n;return Object.keys(t).forEach((e=>{t[e]=r("",t[e])})),t}return"array"===a?n.map((t=>r("",t))):n}const c=n=>{switch(e(n)){case"function":case"symbol":return"";case"date":return t.date+new Date(n).toISOString();case"undefined":return t.undefined;default:return n}};function a(t,e){return s(t.replace(/'/g,'"').replace(/%27/g,"'"),e)}function s(t,e){try{return JSON.parse(t,u)}catch{return e}}function u(t,e){return"string"==typeof e?i(e):e}const i=e=>{if(e!==t.undefined)return e?.startsWith?.(t.date)?new Date(e.slice(1)):e},o=new RegExp(`^(${t.undefined}|${t.date})`),f=t=>o.test(String(t));export{a as decode,i as decodePrimitive,n as encode,c as encodePrimitive,s as parseJSON,u as reviver};
|
|
@@ -46,7 +46,7 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState: T,
|
|
|
46
46
|
* // same as setState(form) with setUrl(form)
|
|
47
47
|
* ```
|
|
48
48
|
*
|
|
49
|
-
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState
|
|
49
|
+
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState}
|
|
50
50
|
*/
|
|
51
51
|
export declare function useUrlState<T extends JSONCompatible>(defaultState: T, params?: Params): {
|
|
52
52
|
urlState: T;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRouter as e,useSearchParams as t}from"next/navigation";import a from"react";import{parseSPObj as r}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as l,isSSR as o,filterUnknownParams as c,filterUnknownParamsClient as u}from"../../utils.mjs";function i(i,p){const n="defaultState"in i?i.defaultState:i,f="defaultState"in i?i.searchParams:p?.searchParams,S="defaultState"in i?i.useHistory:p?.useHistory,d="defaultState"in i?{scroll:i.scroll,replace:i.replace}:{scroll:p?.scroll,replace:p?.replace},j=void 0===S||S?l:e(),{state:U,updateState:b,updateUrl:g,reset:P,getState:h}=s(n,j,(({parse:e})=>o
|
|
1
|
+
import{useRouter as e,useSearchParams as t}from"next/navigation";import a from"react";import{parseSPObj as r}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as l,isSSR as o,filterUnknownParams as c,filterUnknownParamsClient as u}from"../../utils.mjs";function i(i,p){const n="defaultState"in i?i.defaultState:i,f="defaultState"in i?i.searchParams:p?.searchParams,S="defaultState"in i?i.useHistory:p?.useHistory,d="defaultState"in i?{scroll:i.scroll,replace:i.replace}:{scroll:p?.scroll,replace:p?.replace},j=void 0===S||S?l:e(),{state:U,updateState:b,updateUrl:g,reset:P,getState:h}=s(n,j,(({parse:e})=>o?r(c(n,f),n):e(u(n)))),k=a.useMemo((()=>({...m,...d})),[]),v=a.useCallback(((e,t)=>g(e,{...k,...t})),[g]),x=t();a.useEffect((()=>{b(c(n,r(Object.fromEntries([...x.entries()]),n)))}),[x]);const y=a.useCallback((e=>{P({...k,...e})}),[P]);return{setState:b,updateState:b,setUrl:v,updateUrl:v,urlState:U,state:U,reset:y,getState:h}}const m={replace:!0,scroll:!1};export{i as useUrlState};
|
package/dist/parseSPObj.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{decode as r}from"./encoder/encoder.mjs";function t(r,t){return{...t,...n(JSON.stringify(r),t)}}function n(r,t){try{return JSON.parse(r,e)}catch{return t}}const e=(t,n)=>{const e="string"==typeof n,o=e&&r(n?.replace?.(/'/g,'"'));return t&&e?o:n};export{t as parseSPObj};
|
|
@@ -43,7 +43,7 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState: T,
|
|
|
43
43
|
* // same as setState(form) with setUrl(form)
|
|
44
44
|
* ```
|
|
45
45
|
*
|
|
46
|
-
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState
|
|
46
|
+
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState}
|
|
47
47
|
*/
|
|
48
48
|
export declare function useUrlState<T extends JSONCompatible>(defaultState: T, params?: Params): {
|
|
49
49
|
urlState: T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const clone: (obj: object) => any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{stateMap as r,subscribers as e}from"../subscribers.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{isSSR as u,isEqual as c}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[a,i]=t.useState((()=>{if(u
|
|
1
|
+
import t from"react";import{stateMap as r,subscribers as e}from"../subscribers.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{isSSR as u,isEqual as c}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[a,i]=t.useState((()=>{if(u)return o?o?.():f.current;const t=r.get(f.current);if(t)return t;{const t=o?.()||f.current;return r.set(f.current,t),t}})),m=t.useCallback((t=>{const n=r.get(f.current),u="function"==typeof t?t(n):{...n,...t};c(n,u)||(r.set(f.current,u),e.get(f.current).forEach((t=>t())))}),[]);n((()=>{const t=e.add(f.current,(()=>{i(r.get(f.current)||f.current)}));return()=>{t()}}),[]);return{state:a,getState:t.useCallback((()=>r.get(f.current)||f.current),[]),setState:m}}export{s as useSharedState};
|
|
@@ -17,7 +17,7 @@ import { type JSONCompatible } from "../utils";
|
|
|
17
17
|
* parse('name=Tom');
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
|
-
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode
|
|
20
|
+
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/useUrlEncode}
|
|
21
21
|
*/
|
|
22
22
|
export declare function useUrlEncode<T extends JSONCompatible>(stateShape: T): {
|
|
23
23
|
/**
|
|
@@ -31,7 +31,7 @@ import { type JSONCompatible, type Router } from "../utils";
|
|
|
31
31
|
*
|
|
32
32
|
* ```
|
|
33
33
|
*
|
|
34
|
-
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase
|
|
34
|
+
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase}
|
|
35
35
|
*/
|
|
36
36
|
export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, getInitialState?: ({ parse, }: {
|
|
37
37
|
parse: ReturnType<typeof useUrlEncode<T>>["parse"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"react";import{
|
|
1
|
+
import e from"react";import{TIMEOUT as t}from"../constants/constants.mjs";import{useInsertionEffect as o}from"../useInsertionEffect.mjs";import{useSharedState as r}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as n}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as s}from"../utils.mjs";function a(a,i,u){const{parse:m,stringify:l}=n(a),{state:p,getState:d,setState:f}=r(a,(()=>u?.({parse:m})||a)),h=e.useRef(void 0);o((()=>{const e=()=>{const e=m(s(a));f(e)};return window.addEventListener(c,e),()=>{window.removeEventListener(c,e),clearTimeout(h.current)}}),[f]);const w=e.useRef([]),S=e.useCallback(((e,o)=>{const r="function"==typeof e?e(d()):e?{...d(),...e}:d(),n=l(r,function(e){const t=Object.keys(e),o=window.location.search,r=new URLSearchParams(o),n=new URLSearchParams;return r.forEach(((e,o)=>!t.includes(o)&&n.set(o,e))),n}(a)),s=`${window.location.pathname}${n.length?"?":""}${n}${window.location.hash}`;if(s===`${window.location.pathname}${window.location.search}${window.location.hash}`)return;let c;f(r);const u=o?.replace;delete o?.replace,w.current.push([u?"replace":"push",s,o]),clearTimeout(h.current),h.current=setTimeout((()=>{queueMicrotask((()=>{for(;w.current.length;){const e=w.current.shift();e&&e?.[1]!==c?.[1]&&(c=e)}const[e,t,o]=c||{};c=void 0,e&&i[e](t,o)}))}),t)}),[i,l,d]),$=e.useCallback((e=>{S(a,e)}),[S]);return{updateState:f,updateUrl:S,state:p,reset:$,getState:d}}const c="popstate";export{a as useUrlStateBase};
|
package/dist/utils.d.ts
CHANGED
|
@@ -8,7 +8,9 @@ export type Type = "string" | "date" | "boolean" | "number" | "bigint" | "undefi
|
|
|
8
8
|
* `function`, `symbol`, `array`
|
|
9
9
|
*/
|
|
10
10
|
export declare const typeOf: (val: unknown) => Type;
|
|
11
|
-
export declare const
|
|
11
|
+
export declare const isPrimitive: (val: unknown) => val is Simple;
|
|
12
|
+
export type Simple = string | Date | boolean | number | null | undefined | Function | symbol;
|
|
13
|
+
export declare const isSSR: boolean;
|
|
12
14
|
export type JSON = null | boolean | Date | number | string | {
|
|
13
15
|
[prop: string]: JSON | JSON[];
|
|
14
16
|
};
|
|
@@ -22,9 +24,11 @@ export type UnknownObj = object | {
|
|
|
22
24
|
export declare const isEqual: (val1: unknown, val2: unknown) => boolean;
|
|
23
25
|
export declare function filterUnknownParamsClient<T extends object>(shape: T): string;
|
|
24
26
|
export declare function filterUnknownParams<T extends object>(shape: T, searchParams?: object): T;
|
|
27
|
+
export declare function filterUnknown<T extends object>(shape: T, entries: [key: string, value: string][]): string[][];
|
|
25
28
|
export declare function assignValue<T extends object>(shape: T, newVal: Partial<T>): T;
|
|
26
29
|
export interface Router {
|
|
27
30
|
push: (href: string, opts: object) => void;
|
|
28
31
|
replace: (href: string, opts: object) => void;
|
|
29
32
|
}
|
|
30
33
|
export declare const routerHistory: Router;
|
|
34
|
+
export declare const isSafari: boolean;
|
package/dist/utils.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=t=>{const
|
|
1
|
+
const t=t=>{const e=typeof t,n=null===t,r=Array.isArray(t),o=t instanceof Date;return(n?"null":o&&"date")||r&&"array"||!n&&!o&&!r&&"object"===e&&"object"||e},e="undefined"==typeof window,n=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",o=(t,e)=>JSON.stringify(t)===JSON.stringify(e);function i(t){const e=new URLSearchParams;return c(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,n])=>e.set(t,n))),e.toString()}function s(t,e){return Object.fromEntries(c(t,Object.entries(e||{})))}function c(t,e){const n=Object.keys(t),r=[];for(let t=0;t<e.length;t++)n.includes(e[t]?.[0])&&r.push([e[t][0].replace(/\+/g," "),e[t][1]]);return r}function a(t,e){const n=Object.assign({},t);return Object.entries(t).forEach((([r])=>{const o=r,i=void 0!==e[o];n[o]=i?e[o]:t[o]})),n}const u={push:t=>{window&&window.history.pushState(null,"",t)},replace:t=>{window&&window.history.replaceState(null,"",t)}},l=/apple/i.test(!e&&navigator?.userAgent||"");export{a as assignValue,c as filterUnknown,s as filterUnknownParams,i as filterUnknownParamsClient,n as getParams,o as isEqual,e as isSSR,l as isSafari,u as routerHistory,t as typeOf};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"description": "Easily share complex state objects between unrelated React components, preserve types and structure, with TS validation. Deep links and url state synchronization wthout any hasssle or boilerplate.",
|
|
5
5
|
"homepage": "https://state-in-url.dev",
|
|
6
6
|
"repository": {
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"reinstall": "npm run cleanup && npm install",
|
|
104
104
|
"setup": "playwright install --with-deps",
|
|
105
105
|
"prepack": "npm run build && cp package.json dist/package.json && npm run build:demo",
|
|
106
|
-
"pin-gh-deps": "pin-github-action .github/workflows/tests.yml",
|
|
106
|
+
"pin-gh-deps": "pin-github-action -i .github/workflows/tests.yml",
|
|
107
107
|
"prepare": "npx husky"
|
|
108
108
|
},
|
|
109
109
|
"wireit": {
|
|
@@ -202,6 +202,12 @@
|
|
|
202
202
|
],
|
|
203
203
|
"command": "cd packages/example-nextjs14 && npm run build"
|
|
204
204
|
},
|
|
205
|
+
"build:demo:tests": {
|
|
206
|
+
"dependencies": [
|
|
207
|
+
"build"
|
|
208
|
+
],
|
|
209
|
+
"command": "cd packages/example-nextjs14 && npx cross-env TEST=true npm run build"
|
|
210
|
+
},
|
|
205
211
|
"start:demo": {
|
|
206
212
|
"dependencies": [
|
|
207
213
|
"build:demo"
|
|
@@ -272,7 +278,7 @@
|
|
|
272
278
|
"dependencies": [
|
|
273
279
|
"build",
|
|
274
280
|
"build:react",
|
|
275
|
-
"build:demo",
|
|
281
|
+
"build:demo:tests",
|
|
276
282
|
"build:next15",
|
|
277
283
|
"build:router6"
|
|
278
284
|
]
|
|
@@ -342,7 +348,7 @@
|
|
|
342
348
|
"@testing-library/react": "^16.0.0",
|
|
343
349
|
"@testing-library/react-hooks": "^8.0.1",
|
|
344
350
|
"@types/glob": "^8.1.0",
|
|
345
|
-
"@types/jest": "^29.5.
|
|
351
|
+
"@types/jest": "^29.5.14",
|
|
346
352
|
"@types/node": "^20.11.24",
|
|
347
353
|
"@types/react": "^18.3.3",
|
|
348
354
|
"@types/react-dom": "^18.3.0",
|
|
@@ -374,7 +380,7 @@
|
|
|
374
380
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
375
381
|
"semantic-release": "^24.1.0",
|
|
376
382
|
"standard-version": "^9.5.0",
|
|
377
|
-
"ts-jest": "^29.
|
|
383
|
+
"ts-jest": "^29.2.5",
|
|
378
384
|
"ts-node": "^10.9.2",
|
|
379
385
|
"typescript": "^5.3.3",
|
|
380
386
|
"wait-on": "^8.0.1",
|