state-in-url 0.2.5 → 0.2.7
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 +30 -77
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/urlstate/constants/index.d.ts +1 -0
- package/dist/urlstate/{encodeState.d.ts → encodeState/encodeState.d.ts} +5 -5
- package/dist/urlstate/encodeState/index.d.ts +1 -0
- package/dist/urlstate/{encoder.d.ts → encoder/encoder.d.ts} +6 -5
- package/dist/urlstate/encoder/index.d.ts +1 -0
- package/dist/urlstate/index.d.ts +5 -5
- package/dist/urlstate/state.d.ts +6 -6
- package/dist/urlstate/subscribers.d.ts +6 -2
- package/dist/urlstate/useUrlEncode/index.d.ts +1 -0
- package/dist/urlstate/useUrlEncode/useUrlEncode.d.ts +45 -0
- package/dist/urlstate/useUrlState/index.d.ts +1 -0
- package/dist/urlstate/useUrlState/useUrlState.d.ts +51 -0
- package/dist/urlstate/utils.d.ts +4 -5
- package/package.json +10 -9
- package/dist/urlstate/useUrlEncode.d.ts +0 -25
- package/dist/urlstate/useUrlState.d.ts +0 -31
- /package/dist/urlstate/{constants.d.ts → constants/constants.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -5,30 +5,31 @@
|
|
|
5
5
|
<div align="center">
|
|
6
6
|
<img src="/assets/logo.svg?raw=true" alt="Library logo"/>
|
|
7
7
|
|
|
8
|
-
<div>
|
|
8
|
+
<div>Easily share state between unrelated React components, with IDE autocomplete and TS validation. Without any hasssle or boilerplate. </div>
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
<div align="center">
|
|
13
13
|
<hr />
|
|
14
14
|
|
|
15
|
-
[](https://www.linkedin.com/in/asmyshlyaev177/)
|
|
16
|
-
|
|
17
15
|
|
|
18
16
|
[](https://www.npmjs.com/package/state-in-url)
|
|
19
17
|

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

|
|
22
21
|
</div>
|
|
23
22
|
|
|
24
23
|
<div align="center">
|
|
25
24
|
|
|
26
|
-
<h4 align="center">Don't hesitate to open issue if you found a bug</h4>
|
|
25
|
+
<h4 align="center">Don't hesitate to open an issue if you found a bug</h4>
|
|
27
26
|
|
|
28
27
|

|
|
29
28
|
|
|
30
29
|
|
|
31
|
-
<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a>
|
|
30
|
+
<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
|
|
31
|
+
<a href="https://stackblitz.com/~/github.com/asmyshlyaev177/state-in-url?file=packages%2Fexample-nextjs%2Fsrc%2Fapp%2FForm.tsx" target="_blank">Demo on Stackblitz</a> |
|
|
32
|
+
<a href ="https://codesandbox.io/p/github/asmyshlyaev177/state-in-url/main?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clyk5bd9y00062v6jspcfrkx7%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%257D%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%257D%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clyk5bd9x00022v6jyg71cr9e%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%252C%2522activeTabId%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%257D%252C%2522clyk5bd9x00052v6j5r632b12%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%252C%2522activeTabId%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522SETUP_TASKS%2522%252C%2522id%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A2222%252C%2522id%2522%253A%2522clyk5cjbo004d2v6j3u55k74g%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522ENV_SETUP%2522%252C%2522id%2522%253A%2522clyk5h8dp000r2v6j0r7kc7qq%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%252C%2522clyk5bd9x00042v6jsos2y043%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%252C%2522activeTabId%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9x00032v6jtfhj316o%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522NEW_TERMINAL%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522id%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522cleanupDist%2522%252C%2522id%2522%253A%2522clyk5dgvo005i2v6jj950sxft%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D" target="_blank">Demo on Codesandbox</a>
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
<hr />
|
|
@@ -41,17 +42,19 @@ Add a ⭐️ to support the project!
|
|
|
41
42
|
|
|
42
43
|
## Why use state-in-url?
|
|
43
44
|
|
|
44
|
-
`state-in-url`
|
|
45
|
+
`state-in-url` Simple state management with URL synchronization.
|
|
45
46
|
|
|
46
47
|
# Use cases
|
|
48
|
+
|
|
49
|
+
- 🙃 Share the state between different components without changing url, good as alternative to signals and other state management tools
|
|
47
50
|
- 🔗 Shareable URLs with full application state
|
|
48
51
|
- 🔄 Easy state persistence across page reloads
|
|
49
|
-
- 🧠
|
|
50
|
-
- 🙃 Can just share the state without changing url
|
|
52
|
+
- 🧠 Sync data between unrelated client components
|
|
51
53
|
- 🧮 Store unsaved user forms in URL
|
|
52
54
|
|
|
53
55
|
# Features
|
|
54
|
-
|
|
56
|
+
|
|
57
|
+
- 🧩 **Simple**: No providers, reducers, boilerplate or new concepts
|
|
55
58
|
- 📘 **Typescript support and type Safety**: Preserves data types and structure, enhances developer experience with IDE suggestions, strong typing and JSDoc comments
|
|
56
59
|
- ⚛️ **Framework Flexibility**: Separate hooks for Next.js and React.js applications, and functions for pure JS
|
|
57
60
|
- ⚙ **Well tested**: Unit tests and Playwright tests
|
|
@@ -60,6 +63,7 @@ Add a ⭐️ to support the project!
|
|
|
60
63
|
|
|
61
64
|
|
|
62
65
|
## Table of content
|
|
66
|
+
|
|
63
67
|
- [Installation](#installation)
|
|
64
68
|
- [`useUrlState` for Next.js](#useurlstate-hook-for-nextjs)
|
|
65
69
|
- [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
|
|
@@ -74,7 +78,9 @@ Add a ⭐️ to support the project!
|
|
|
74
78
|
- [Inspiration](#inspiration)
|
|
75
79
|
|
|
76
80
|
## installation
|
|
81
|
+
|
|
77
82
|
### 1. Install package
|
|
83
|
+
|
|
78
84
|
```sh
|
|
79
85
|
# npm
|
|
80
86
|
npm install --save state-in-url
|
|
@@ -83,20 +89,23 @@ yarn add state-in-url
|
|
|
83
89
|
# pnpm
|
|
84
90
|
pnpm add state-in-url
|
|
85
91
|
```
|
|
92
|
+
|
|
86
93
|
### 2. Edit tsconfig.json
|
|
87
|
-
set `"moduleResolution": "Node16"` or `"moduleResolution": "NodeNext"` in your `tsconfig.json`
|
|
88
94
|
|
|
95
|
+
set `"moduleResolution": "Node16"` or `"moduleResolution": "NodeNext"` in your `tsconfig.json`
|
|
89
96
|
|
|
90
97
|
## useUrlState hook for Next.js
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
[Docs](packages/urlstate/useUrlState/README.md)
|
|
93
100
|
|
|
101
|
+
`useUrlState` is a custom React hook for Next.js applications that make communication between client components easy. It allows you to share any complex state and sync it with the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.
|
|
94
102
|
|
|
95
103
|
### Usage examples
|
|
96
104
|
|
|
97
105
|
#### Basic
|
|
98
106
|
|
|
99
107
|
1. Define state shape
|
|
108
|
+
|
|
100
109
|
```typescript
|
|
101
110
|
// countState.ts
|
|
102
111
|
// State shape should be stored in a constant, don't pass an object directly
|
|
@@ -106,6 +115,7 @@ set `"moduleResolution": "Node16"` or `"moduleResolution": "NodeNext"` in your `
|
|
|
106
115
|
```
|
|
107
116
|
|
|
108
117
|
2. Import it and use
|
|
118
|
+
|
|
109
119
|
```typescript
|
|
110
120
|
'use client'
|
|
111
121
|
import { useUrlState } from 'state-in-url';
|
|
@@ -247,79 +257,17 @@ function SettingsComponent() {
|
|
|
247
257
|
}
|
|
248
258
|
```
|
|
249
259
|
|
|
250
|
-
## useUrlEncode hook for React.js
|
|
251
|
-
|
|
252
|
-
`useUrlEncode` is a custom React hook that provides utility functions for encoding and decoding state object to and from URL search parameters. This hook doesn't depend on Nextjs, and will works with any React application.
|
|
253
|
-
|
|
254
|
-
Accepts optional `defaultState` argument.
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
import { useUrlEncode } from 'state-in-url';
|
|
260
|
+
## `useUrlEncode` hook for React.js
|
|
258
261
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const Component = () => {
|
|
262
|
-
const { parse, stringify } = useUrlEncode();
|
|
263
|
-
|
|
264
|
-
const str = stringify({ age: 36 }); // age=∓36
|
|
265
|
-
const obj = parse(str, ageState); // { age: 36 }
|
|
266
|
-
// Or const obj = parse(str); // { age: 36 }
|
|
267
|
-
|
|
268
|
-
const currentParams = parse(window.location.search);
|
|
269
|
-
// OR
|
|
270
|
-
// const obj = parse(new URLSearchParams(window.location.search))
|
|
271
|
-
|
|
272
|
-
const updateSearch = () => {
|
|
273
|
-
const currentParams = new URLSearchParams(window.location.search);
|
|
274
|
-
const newState = { query: 'react hooks', page: 2 };
|
|
275
|
-
const updatedParamsString = stringify(newState, currentParams);
|
|
276
|
-
console.log(updatedParamsString);
|
|
277
|
-
// Output: existing params + query=react%20hooks&page=2
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
```
|
|
262
|
+
[Docs](packages/urlstate/useUrlEncode/README.md)
|
|
281
263
|
|
|
282
264
|
## `encodeState` and `decodeState` helpers
|
|
283
265
|
|
|
284
|
-
|
|
285
|
-
```ts
|
|
286
|
-
export const form = { name: '' };
|
|
287
|
-
...
|
|
288
|
-
encodeState({ name: 'test' }, form, 'someExistingParam=123');
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
`decodeState` let you decode queryString with optional defaults
|
|
292
|
-
```ts
|
|
293
|
-
export const form = { name: '' };
|
|
294
|
-
...
|
|
295
|
-
decodeState('name=Alex', form);
|
|
296
|
-
```
|
|
266
|
+
[Docs](packages/urlstate/encodeState/README.md)
|
|
297
267
|
|
|
298
268
|
## `encode` and `decode` helpers
|
|
299
269
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
```javascript
|
|
303
|
-
import { encode, decode } from 'state-in-url';
|
|
304
|
-
|
|
305
|
-
const state = { obj: [1, 2, 3], obj2: true }
|
|
306
|
-
|
|
307
|
-
// to params
|
|
308
|
-
const params = new URLSearchParams();
|
|
309
|
-
Object.entries(state).forEach(([key, value]) => {
|
|
310
|
-
params.set(key, encode(value));
|
|
311
|
-
});
|
|
312
|
-
const str = params.toString();
|
|
313
|
-
|
|
314
|
-
// from params
|
|
315
|
-
const obj = Object.fromEntries(
|
|
316
|
-
[...params.entries()].map(([key, value]) => [
|
|
317
|
-
key,
|
|
318
|
-
// decode(value, optionalFallback),
|
|
319
|
-
decode(value),
|
|
320
|
-
]),
|
|
321
|
-
)
|
|
322
|
-
```
|
|
270
|
+
[Docs](packages/urlstate/encoder/README.md)
|
|
323
271
|
|
|
324
272
|
## Best Practices
|
|
325
273
|
|
|
@@ -334,10 +282,13 @@ const obj = Object.fromEntries(
|
|
|
334
282
|
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
|
|
335
283
|
|
|
336
284
|
## Run locally
|
|
285
|
+
|
|
337
286
|
Clone this repo, run `npm install` and
|
|
287
|
+
|
|
338
288
|
```sh
|
|
339
289
|
npm run dev
|
|
340
290
|
```
|
|
291
|
+
|
|
341
292
|
Go to [localhost:3000](http://localhost:3000)
|
|
342
293
|
|
|
343
294
|
|
|
@@ -348,9 +299,11 @@ Go to [localhost:3000](http://localhost:3000)
|
|
|
348
299
|
## [Changelog](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
|
|
349
300
|
|
|
350
301
|
## License
|
|
302
|
+
|
|
351
303
|
This project is licensed under the [MIT license](https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE).
|
|
352
304
|
|
|
353
305
|
## Inspiration
|
|
306
|
+
|
|
354
307
|
[Using URL to store state in Vue ](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)
|
|
355
308
|
|
|
356
309
|
[Storing state in the URL](https://antonz.org/storing-state/)
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=require("react"),e=require("next/navigation");function n(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var r=n(t);const c={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"},o=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},s=()=>"undefined"==typeof window,u=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),a=(t,e)=>JSON.stringify(t)===JSON.stringify(e);function i(t){switch(o(t)){case"function":case"symbol":return"";case"date":return c.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${c.string}${encodeURIComponent(t)}`;case"number":return c.number+String(t);case"boolean":return c.boolean+String(t);case"object":case"array":return JSON.stringify(t,f).replaceAll('"',"'");case"null":return c.null;case"undefined":return c.undefined;default:return String(t)}}function l(t,e){return function(t,e){try{return JSON.parse(t,g)}catch{const n=d(t);return n!==p?n:e}}(t.replaceAll("'",'"'),e)}const f=(t,e)=>{const n=o(e);return t&&"object"!==n&&"array"!==n?i(e):e},d=t=>t===c.null?null:t!==c.undefined?t?.startsWith?.(c.number)?Number.parseFloat(t.replace(c.number,"")):t?.startsWith?.(c.boolean)?!!t.includes("true"):t?.startsWith?.(c.date)?new Date(t.slice(1)):t?.startsWith?.(c.string)?decodeURIComponent(t).replace(/^◖/,""):p:void 0,p=Symbol("isError"),g=(t,e)=>{const n="string"==typeof e,r=n&&d(e);return r===p?e:t&&n?r:e};const y=(t,e)=>{const n="string"==typeof e,r=n&&l(e?.replaceAll?.("'",'"'));return t&&n?r:e};function b(t,e){try{return JSON.parse(t,y)}catch{const n=d(t);return n!==p?n:e}}function S(t,e,n){const r=u(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,i(n))})),r.toString()}function w(t,e){return{...e||{},...Object.fromEntries([...u(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,l(n,r)??r]})))}}function h(t){const e=r.default.useCallback((function(e,n){return"object"===o(e)?S(e,t,n):""}),[t]);return{parse:r.default.useCallback((function(e){return w(e,t)}),[t]),stringify:e}}const m=new WeakMap,O=new WeakMap,j={get:function(t){return m.get(t)},set:function(t,e){m.set(t,e)}};exports.decode=l,exports.decodeState=w,exports.encode=i,exports.encodeState=S,exports.useUrlEncode=h,exports.useUrlState=function(t,n){const{state:c,getState:o,setState:u,stringify:i}=function(t){const e=r.default.useRef(t),{parse:n,stringify:c}=h(e.current),[o,u]=r.default.useState((()=>{const t=j.get(e.current),r=n(s()?"":window.location.search);return t||(j.set(e.current,r),r)})),i=r.default.useCallback((t=>{const n=j.get(e.current)||e.current;if("function"==typeof t){const r=t(n);if(a(n,r))return;j.set(e.current,r),(O.get(e.current)||[]).forEach((t=>{t()}))}else{if(a(n,t))return;j.set(e.current,t),(O.get(e.current)||[]).forEach((t=>{t()}))}}),[]);return r.default.useInsertionEffect((()=>{const t=O.get(e.current)||[],r=()=>{u(j.get(e.current)||e.current)};O.set(e.current,t.concat(r));const c=()=>{const t=n(window.location.search);i(t)},o="popstate";return window.addEventListener(o,c),()=>{const t=O.get(e.current)||[];O.set(e.current,t.filter((t=>t!==r))),window.removeEventListener(o,c)}}),[i]),{state:o,getState:r.default.useCallback((()=>j.get(e.current)||e.current),[]),setState:i,stringify:c}}(s()?function(t,e){return{...e,...b(JSON.stringify(t),e)}}(n,t):t),l=e.useRouter(),f=r.default.useCallback(((t,e)=>{const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`;let c,s;"function"==typeof t?(c=t(o()),s=i(c)):(c=t??o(),s=i(c)),u(c);const a=`${window.location.pathname}${s.length?"?":""}${s}`;if(r!==a){const{replace:t,...n}=e||{};l[t?"replace":"push"](a,{scroll:!1,...n})}}),[l,i,o]);return{updateState:u,updateUrl:f,state:c}};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},
|
|
1
|
+
import t from"react";import{useRouter as e}from"next/navigation";const n={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"},r=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},c=()=>"undefined"==typeof window,o=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),s=(t,e)=>JSON.stringify(t)===JSON.stringify(e);function a(t){switch(r(t)){case"function":case"symbol":return"";case"date":return n.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${n.string}${encodeURIComponent(t)}`;case"number":return n.number+String(t);case"boolean":return n.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return n.null;case"undefined":return n.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const n=r(e);return t&&"object"!==n&&"array"!==n?a(e):e},l=t=>t===n.null?null:t!==n.undefined?t?.startsWith?.(n.number)?Number.parseFloat(t.replace(n.number,"")):t?.startsWith?.(n.boolean)?!!t.includes("true"):t?.startsWith?.(n.date)?new Date(t.slice(1)):t?.startsWith?.(n.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),g=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};const d=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return t&&n?r:e};function p(t,e){try{return JSON.parse(t,d)}catch{const n=l(t);return n!==f?n:e}}function y(t,e,n){const r=o(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,a(n))})),r.toString()}function b(t,e){return{...e||{},...Object.fromEntries([...o(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function S(e){const n=t.useCallback((function(t,n){return"object"===r(t)?y(t,e,n):""}),[e]);return{parse:t.useCallback((function(t){return b(t,e)}),[e]),stringify:n}}const m=new WeakMap,w=new WeakMap,h={get:function(t){return m.get(t)},set:function(t,e){m.set(t,e)}};function O(n,r){const{state:o,getState:a,setState:u,stringify:i}=function(e){const n=t.useRef(e),{parse:r,stringify:o}=S(n.current),[a,u]=t.useState((()=>{const t=h.get(n.current),e=r(c()?"":window.location.search);return t||(h.set(n.current,e),e)})),i=t.useCallback((t=>{const e=h.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(s(e,r))return;h.set(n.current,r),(w.get(n.current)||[]).forEach((t=>{t()}))}else{if(s(e,t))return;h.set(n.current,t),(w.get(n.current)||[]).forEach((t=>{t()}))}}),[]);return t.useInsertionEffect((()=>{const t=w.get(n.current)||[],e=()=>{u(h.get(n.current)||n.current)};w.set(n.current,t.concat(e));const c=()=>{const t=r(window.location.search);i(t)},o="popstate";return window.addEventListener(o,c),()=>{const t=w.get(n.current)||[];w.set(n.current,t.filter((t=>t!==e))),window.removeEventListener(o,c)}}),[i]),{state:a,getState:t.useCallback((()=>h.get(n.current)||n.current),[]),setState:i,stringify:o}}(c()?function(t,e){return{...e,...p(JSON.stringify(t),e)}}(r,n):n),l=e(),f=t.useCallback(((t,e)=>{const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`;let c,o;"function"==typeof t?(c=t(a()),o=i(c)):(c=t??a(),o=i(c)),u(c);const s=`${window.location.pathname}${o.length?"?":""}${o}`;if(r!==s){const{replace:t,...n}=e||{};l[t?"replace":"push"](s,{scroll:!1,...n})}}),[l,i,a]);return{updateState:u,updateUrl:f,state:o}}export{u as decode,b as decodeState,a as encode,y as encodeState,S as useUrlEncode,O as useUrlState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './constants';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type JSONCompatible, type UnknownObj } from '
|
|
1
|
+
import { type JSONCompatible, type UnknownObj } from '../utils';
|
|
2
2
|
/**
|
|
3
3
|
* Encodes the state object into a URL query string.
|
|
4
4
|
*
|
|
@@ -12,9 +12,9 @@ import { type JSONCompatible, type UnknownObj } from './utils';
|
|
|
12
12
|
* encodeState({ name: 'test' }, form, 'someExistingParam=123');
|
|
13
13
|
* ```
|
|
14
14
|
*
|
|
15
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
15
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/encodeState#encodestate}
|
|
16
16
|
*/
|
|
17
|
-
export declare function encodeState<T>(state:
|
|
17
|
+
export declare function encodeState<T extends JSONCompatible>(state: T, defaults?: T, paramsToKeep?: string | URLSearchParams): string;
|
|
18
18
|
/**
|
|
19
19
|
* Decodes a URI string into an object of type T.
|
|
20
20
|
*
|
|
@@ -27,6 +27,6 @@ export declare function encodeState<T>(state: never extends T ? object : JSONCom
|
|
|
27
27
|
* decodeState('key=value&name=Alex', form);
|
|
28
28
|
* ```
|
|
29
29
|
*
|
|
30
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
30
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/encodeState#decodestate}
|
|
31
31
|
*/
|
|
32
|
-
export declare function decodeState<T>(uriString: string | URLSearchParams, defaults?:
|
|
32
|
+
export declare function decodeState<T extends JSONCompatible>(uriString: string | URLSearchParams, defaults?: T): undefined extends T ? UnknownObj : T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './encodeState';
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { type JSONCompatible } from '../utils';
|
|
1
2
|
/**
|
|
2
3
|
* Encode any JSON serializable value to URL friendly string
|
|
3
4
|
*
|
|
4
5
|
* @param {unknown} payload any value to serialize
|
|
5
6
|
* @returns {string} URLSearchParams compatible value string
|
|
6
7
|
*
|
|
7
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
8
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/encoder#encode}
|
|
8
9
|
*/
|
|
9
10
|
export declare function encode(payload: unknown): string;
|
|
10
11
|
/**
|
|
@@ -14,9 +15,9 @@ export declare function encode(payload: unknown): string;
|
|
|
14
15
|
* @param {any} fallback optional fallback value
|
|
15
16
|
* @returns {unknown} decoded object
|
|
16
17
|
*
|
|
17
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
18
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/encoder#decode}
|
|
18
19
|
*/
|
|
19
|
-
export declare function decode<T>(payload: string, fallback?: T):
|
|
20
|
+
export declare function decode<T>(payload: string, fallback?: T): JSONCompatible | Primitive;
|
|
20
21
|
export declare const replacer: (key: string, value: unknown) => unknown;
|
|
21
22
|
export type Primitive = Exclude<ReturnType<typeof decodePrimitive>, typeof errorSym>;
|
|
22
23
|
export declare const decodePrimitive: (str: string) => string | number | boolean | Date | typeof errorSym | null | undefined;
|
|
@@ -31,5 +32,5 @@ export declare const reviver: (key: string, value: unknown) => unknown;
|
|
|
31
32
|
*
|
|
32
33
|
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
33
34
|
*/
|
|
34
|
-
export declare function parseJSON<T>(jsonString: string, fallbackValue?: T): T | Primitive | undefined;
|
|
35
|
-
export declare function parseSsrQs<T>(sp: object | undefined, defaults: T): T;
|
|
35
|
+
export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T | Primitive | undefined;
|
|
36
|
+
export declare function parseSsrQs<T extends JSONCompatible>(sp: object | undefined, defaults: T): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './encoder';
|
package/dist/urlstate/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { decode, encode, parseSsrQs } from './encoder';
|
|
2
|
+
import { decodeState, encodeState } from './encodeState';
|
|
3
3
|
import { useUrlEncode } from './useUrlEncode';
|
|
4
4
|
import { useUrlState } from './useUrlState';
|
|
5
|
-
import {
|
|
6
|
-
export {
|
|
7
|
-
export type {
|
|
5
|
+
import { isSSR, type JSONCompatible, type Type, typeOf } from './utils';
|
|
6
|
+
export { decode, decodeState, encode, encodeState, isSSR, parseSsrQs, typeOf, useUrlEncode, useUrlState, };
|
|
7
|
+
export type { JSONCompatible, Type };
|
package/dist/urlstate/state.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type JSONCompatible
|
|
2
|
-
export declare function useState<T>(defaultState:
|
|
3
|
-
state:
|
|
4
|
-
getState: () =>
|
|
5
|
-
setState: (value:
|
|
6
|
-
stringify: (state:
|
|
1
|
+
import { type JSONCompatible } from './utils';
|
|
2
|
+
export declare function useState<T extends JSONCompatible>(defaultState: T): {
|
|
3
|
+
state: T;
|
|
4
|
+
getState: () => T;
|
|
5
|
+
setState: (value: T | ((currState: T) => T)) => void;
|
|
6
|
+
stringify: (state: T, paramsToKeep?: string | URLSearchParams | undefined) => string;
|
|
7
7
|
};
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const subscribers: WeakMap<
|
|
1
|
+
import { type JSONCompatible } from './utils';
|
|
2
|
+
export declare const subscribers: WeakMap<JSONCompatible, Cb[]>;
|
|
3
3
|
export type Cb = () => void;
|
|
4
|
+
export declare const stateMap: {
|
|
5
|
+
get: <T extends JSONCompatible>(objKey: T) => T;
|
|
6
|
+
set: <T_1 extends JSONCompatible>(objKey: T_1, value: T_1) => void;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useUrlEncode';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type JSONCompatible } from '../utils';
|
|
2
|
+
/**
|
|
3
|
+
* A hook that returns stringify and parse functions for encoding and decoding state
|
|
4
|
+
* to and from URL search parameters.
|
|
5
|
+
*
|
|
6
|
+
* @template T - The type of the state shape
|
|
7
|
+
* @param {JSONCompatible<T>} [stateShape] Fallback values for state
|
|
8
|
+
* @returns {} `stringify` and `parse` functions
|
|
9
|
+
*
|
|
10
|
+
* * Example:
|
|
11
|
+
* ```ts
|
|
12
|
+
* export const form = { name: '' };
|
|
13
|
+
* const { parse, stringify } = useUrlEncode(form);
|
|
14
|
+
*
|
|
15
|
+
* stringify({ name: 'John' }, 'someExistingParamToKeep=123');
|
|
16
|
+
* // by default it's uses router.push with scroll: false
|
|
17
|
+
* parse('name=Tom');
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlEncode#api}
|
|
21
|
+
*/
|
|
22
|
+
export declare function useUrlEncode<T extends JSONCompatible>(stateShape: T): {
|
|
23
|
+
/**
|
|
24
|
+
Parse URL query string to state object
|
|
25
|
+
* * Example:
|
|
26
|
+
* ```ts
|
|
27
|
+
const state = parse('name=◖Tom');
|
|
28
|
+
console.log(state); // Output: { name: 'Tom' }
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlEncode#parse}
|
|
32
|
+
*/
|
|
33
|
+
parse: (strOrSearchParams: string | URLSearchParams) => T;
|
|
34
|
+
/**
|
|
35
|
+
Converts a state object to a URL query string.
|
|
36
|
+
* * Example:
|
|
37
|
+
* ```ts
|
|
38
|
+
const queryString = stringify({ name: 'John' }, 'someExistingParamToKeep=123');
|
|
39
|
+
console.log(queryString); // Output: name=◖John&someExistingParamToKeep=123
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlEncode#stringify}
|
|
43
|
+
*/
|
|
44
|
+
stringify: (state: typeof stateShape, paramsToKeep?: string | URLSearchParams) => string;
|
|
45
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useUrlState';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useRouter } from 'next/navigation';
|
|
2
|
+
import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
3
|
+
/**
|
|
4
|
+
* NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
|
|
5
|
+
*
|
|
6
|
+
* @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
|
|
7
|
+
* @param {?SearchParams<T>} [searchParams] searchParams from Next server component
|
|
8
|
+
*
|
|
9
|
+
* * Example:
|
|
10
|
+
* ```ts
|
|
11
|
+
* export const form = { name: '' };
|
|
12
|
+
* const { state, updateState, updateUrl } = useUrlState(form);
|
|
13
|
+
*
|
|
14
|
+
* updateState({ name: 'test' });
|
|
15
|
+
* // by default it's uses router.push with scroll: false
|
|
16
|
+
* updateUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlState#api}
|
|
20
|
+
*/
|
|
21
|
+
export declare function useUrlState<T extends JSONCompatible>(defaultState: T, sp?: object): {
|
|
22
|
+
/**
|
|
23
|
+
* * Example:
|
|
24
|
+
* ```ts
|
|
25
|
+
* updateState({ name: 'test' });
|
|
26
|
+
* // or
|
|
27
|
+
* updateState(curr => ({ ...curr, name: 'test' }) );
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlState#updatestate}
|
|
31
|
+
*/
|
|
32
|
+
updateState: (value: T | ((currState: T) => T)) => void;
|
|
33
|
+
/**
|
|
34
|
+
* * Example:
|
|
35
|
+
* ```ts
|
|
36
|
+
* updateUrl({ name: 'test' });
|
|
37
|
+
* // or
|
|
38
|
+
* updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlState#updateurl}
|
|
42
|
+
*/
|
|
43
|
+
updateUrl: (value?: T | DeepReadonly<T> | ((currState: T) => T) | undefined, options?: Options) => void;
|
|
44
|
+
state: DeepReadonly<T>;
|
|
45
|
+
};
|
|
46
|
+
type Router = ReturnType<typeof useRouter>;
|
|
47
|
+
type RouterOptions = NonNullable<Parameters<Router['push']>[1]>;
|
|
48
|
+
interface Options extends RouterOptions {
|
|
49
|
+
replace?: boolean;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
package/dist/urlstate/utils.d.ts
CHANGED
|
@@ -9,11 +9,11 @@ export type Type = 'string' | 'date' | 'boolean' | 'number' | 'bigint' | 'undefi
|
|
|
9
9
|
*/
|
|
10
10
|
export declare const typeOf: (val: unknown) => Type;
|
|
11
11
|
export declare const isSSR: () => boolean;
|
|
12
|
-
type
|
|
13
|
-
[prop: string]:
|
|
12
|
+
export type JSON = null | boolean | Date | number | string | {
|
|
13
|
+
[prop: string]: JSON | JSON[];
|
|
14
14
|
};
|
|
15
|
-
export type JSONCompatible
|
|
16
|
-
[
|
|
15
|
+
export type JSONCompatible = {
|
|
16
|
+
[prop: string]: JSON | JSON[];
|
|
17
17
|
};
|
|
18
18
|
export type DeepReadonly<T> = T extends Map<infer K, infer V> ? ReadonlyMap<DeepReadonly<K>, DeepReadonly<V>> : T extends Set<infer S> ? ReadonlySet<DeepReadonly<S>> : T extends object ? {
|
|
19
19
|
readonly [K in keyof T]: DeepReadonly<T[K]>;
|
|
@@ -23,4 +23,3 @@ export type UnknownObj = {
|
|
|
23
23
|
[key: string]: unknown;
|
|
24
24
|
};
|
|
25
25
|
export declare const isEqual: (val1: unknown, val2: unknown) => boolean;
|
|
26
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Library to store state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers.",
|
|
5
5
|
"homepage": "https://github.com/asmyshlyaev177/state-in-url#readme",
|
|
6
6
|
"keywords": [
|
|
@@ -65,18 +65,12 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"types": "./dist/index.d.ts",
|
|
68
|
-
"typesVersions": {
|
|
69
|
-
"*": {
|
|
70
|
-
"*": [
|
|
71
|
-
"dist/types/"
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
68
|
"scripts": {
|
|
76
69
|
"test:unit": "wireit",
|
|
77
70
|
"test:int": "playwright test",
|
|
78
71
|
"test:int:watch": "npx chokidar-cli 'packages/**/*.ts' 'packages/**/*.tsx' -i '.next' -t 500 -c 'npx playwright test -j 2 --project=chromium'",
|
|
79
72
|
"test": "wireit",
|
|
73
|
+
"tsc": "wireit",
|
|
80
74
|
"dev": "wireit",
|
|
81
75
|
"dev:lib": "wireit",
|
|
82
76
|
"dev:nextjs": "wireit",
|
|
@@ -94,13 +88,20 @@
|
|
|
94
88
|
"wireit": {
|
|
95
89
|
"test": {
|
|
96
90
|
"dependencies": [
|
|
91
|
+
"tsc",
|
|
97
92
|
"build",
|
|
98
93
|
"test:unit",
|
|
99
94
|
"dev:nextjs"
|
|
100
95
|
],
|
|
101
96
|
"command": "npx playwright test --project=chromium -j 2"
|
|
102
97
|
},
|
|
98
|
+
"tsc": {
|
|
99
|
+
"command": "npx tsc --noEmit"
|
|
100
|
+
},
|
|
103
101
|
"test:unit": {
|
|
102
|
+
"dependencies": [
|
|
103
|
+
"tsc"
|
|
104
|
+
],
|
|
104
105
|
"command": "npx jest"
|
|
105
106
|
},
|
|
106
107
|
"dev": {
|
|
@@ -184,7 +185,7 @@
|
|
|
184
185
|
"cz": "^1.8.2",
|
|
185
186
|
"cz-conventional-changelog": "^3.3.0",
|
|
186
187
|
"eslint": "^8.57.0",
|
|
187
|
-
"eslint-plugin-maintainable": "^0.
|
|
188
|
+
"eslint-plugin-maintainable": "^0.4.4",
|
|
188
189
|
"husky": "^9.0.11",
|
|
189
190
|
"jest": "^29.7.0",
|
|
190
191
|
"jest-environment-jsdom": "^29.7.0",
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { type JSONCompatible, type DeepReadonly } from './utils';
|
|
2
|
-
/**
|
|
3
|
-
* A hook that returns stringify and parse functions for encoding and decoding state
|
|
4
|
-
* to and from URL search parameters.
|
|
5
|
-
*
|
|
6
|
-
* @template T - The type of the state shape
|
|
7
|
-
* @param {JSONCompatible<T>} [stateShape] Fallback values for state
|
|
8
|
-
* @returns {} `stringify` and `parse` functions
|
|
9
|
-
*
|
|
10
|
-
* * Example:
|
|
11
|
-
* ```ts
|
|
12
|
-
* export const form = { name: '' };
|
|
13
|
-
* const { parse, stringify } = useUrlEncode(form);
|
|
14
|
-
*
|
|
15
|
-
* stringify({ name: 'John' }, 'someExistingParamToKeep=123');
|
|
16
|
-
* // by default it's uses router.push with scroll: false
|
|
17
|
-
* parse('name=Tom');
|
|
18
|
-
* ```
|
|
19
|
-
*
|
|
20
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
21
|
-
*/
|
|
22
|
-
export declare function useUrlEncode<T>(stateShape: JSONCompatible<T>): {
|
|
23
|
-
parse: (strOrSearchParams: string | URLSearchParams) => DeepReadonly<JSONCompatible<T>>;
|
|
24
|
-
stringify: (state: typeof stateShape | DeepReadonly<typeof stateShape>, paramsToKeep?: string | URLSearchParams) => string;
|
|
25
|
-
};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { useRouter } from 'next/navigation';
|
|
2
|
-
import { DeepReadonly, type JSONCompatible } from './utils';
|
|
3
|
-
/**
|
|
4
|
-
* NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
|
|
5
|
-
*
|
|
6
|
-
* @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
|
|
7
|
-
* @param {?SearchParams<T>} [searchParams] searchParams from Next server component
|
|
8
|
-
*
|
|
9
|
-
* * Example:
|
|
10
|
-
* ```ts
|
|
11
|
-
* export const form = { name: '' };
|
|
12
|
-
* const { state, updateState, updateUrl } = useUrlState(form);
|
|
13
|
-
*
|
|
14
|
-
* updateState({ name: 'test' });
|
|
15
|
-
* // by default it's uses router.push with scroll: false
|
|
16
|
-
* updateUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
17
|
-
* ```
|
|
18
|
-
*
|
|
19
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
20
|
-
*/
|
|
21
|
-
export declare function useUrlState<T>(defaultState: JSONCompatible<T>, sp?: object): {
|
|
22
|
-
updateUrl: (value?: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: typeof defaultState | DeepReadonly<typeof defaultState>) => typeof defaultState | DeepReadonly<typeof defaultState>) | undefined, options?: Options) => void;
|
|
23
|
-
updateState: (value: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>) => JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>)) => false | undefined;
|
|
24
|
-
state: DeepReadonly<JSONCompatible<T>>;
|
|
25
|
-
};
|
|
26
|
-
type Router = ReturnType<typeof useRouter>;
|
|
27
|
-
type RouterOptions = NonNullable<Parameters<Router['push']>[1]>;
|
|
28
|
-
interface Options extends RouterOptions {
|
|
29
|
-
replace?: boolean;
|
|
30
|
-
}
|
|
31
|
-
export {};
|
|
File without changes
|