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 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>Seamlessly store complex nested state object in URL query parameters with IDE autotomplete and TS validation.</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
- [![For hire](/assets/hireBadge.svg)](https://www.linkedin.com/in/asmyshlyaev177/)
16
-
17
15
 
18
16
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
19
17
  ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
18
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/73be54068b7f41b0b74a252579ac09ec)](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
20
19
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/state-in-url/)
21
20
  ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
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
  ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
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` lets you use URI string for state management.
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
- - 🧠 Pass data between unrelated client components
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
- - 🧩 **Simple**: Handles complex objects without extra effort
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
- `useUrlState` is a custom React hook for Next.js applications that make communication between client components easy. It allows you to store and retrieve state from the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.
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
- const ageState = { age: 0 };
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
- `encodeState` let you encode some object with optional `defaults`, and optional existing queryString
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
- There low level helpers to stringify and parse query string params. Useful for other frameworks or pure JS.
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 e=require("react"),t=require("next/navigation");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e);const o=e=>{const t=null===e,n=Array.isArray(e),r=e instanceof Date;return(t?"null":r&&"date")||n&&"array"||!t&&!r&&!n&&"object"==typeof e&&"object"||typeof e},c=()=>"undefined"==typeof window,s=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||""),u=(e,t)=>JSON.stringify(e)===JSON.stringify(t),a={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function i(e){switch(o(e)){case"function":case"symbol":return"";case"date":return a.date+e.toISOString();case"string":return e.match(/^◖/)?encodeURIComponent(e):`${a.string}${encodeURIComponent(e)}`;case"number":return a.number+String(e);case"boolean":return a.boolean+String(e);case"object":case"array":return JSON.stringify(e,f).replaceAll('"',"'");case"null":return a.null;case"undefined":return a.undefined;default:return String(e)}}function l(e,t){return function(e,t){try{return JSON.parse(e,g)}catch{const n=d(e);return n!==p?n:t}}(e.replaceAll("'",'"'),t)}const f=(e,t)=>{const n=o(t);return e&&"object"!==n&&"array"!==n?i(t):t},d=e=>e===a.null?null:e!==a.undefined?e?.startsWith?.(a.number)?Number.parseFloat(e.replace(a.number,"")):e?.startsWith?.(a.boolean)?!!e.includes("true"):e?.startsWith?.(a.date)?new Date(e.slice(1)):e?.startsWith?.(a.string)?decodeURIComponent(e).replace(/^◖/,""):p:void 0,p=Symbol("isError"),g=(e,t)=>{const n="string"==typeof t,r=n&&d(t);return r===p?t:e&&n?r:t};const y=(e,t)=>{const n="string"==typeof t,r=n&&l(t?.replaceAll?.("'",'"'));return r===p?t:e&&n?r:t};function b(e,t){try{return JSON.parse(e,y)}catch{const n=d(e);return n!==p?n:t}}function S(e,t,n){const r=s(n);return Object.entries(e||{}).forEach((([e,n])=>{const o=t?.[e];JSON.stringify(n)!==JSON.stringify(o)&&r.set(e,i(n))})),r.toString()}function w(e,t){return{...t||{},...Object.fromEntries([...s(e).entries()].map((([e,n])=>{const r=t?.[e];return[e,l(n,r)??r]})))}}function h(e){const t=r.default.useCallback((function(t,n){return"object"===o(t)?S(t,e,n):""}),[e]);return{parse:r.default.useCallback((function(t){return w(t,e)}),[e]),stringify:t}}const m=new WeakMap,O=new WeakMap;exports.decode=l,exports.decodeState=w,exports.encode=i,exports.encodeState=S,exports.useUrlEncode=h,exports.useUrlState=function(e,n){const{state:o,getState:s,setState:a,stringify:i}=function(e){const t=r.default.useRef(e),{parse:n,stringify:o}=h(t.current),[s,a]=r.default.useState((()=>{const e=m.get(t.current),r=n(c()?"":window.location.search);return e||m.set(t.current,r),r})),i=r.default.useCallback((e=>{const n=m.get(t.current)||t.current;if("function"==typeof e){const r=e(n);if(u(n,r))return!1;m.set(t.current,r),(O.get(t.current)||[]).forEach((e=>{e()}))}else{if(u(n,e))return!1;m.set(t.current,e),(O.get(t.current)||[]).forEach((e=>{e()}))}}),[]);return r.default.useInsertionEffect((()=>{const r=O.get(t.current)||[],o=()=>{a(m.get(t.current)||e)};O.set(t.current,r.concat(o));const c=()=>{const e=n(window.location.search);i(e)},s="popstate";return window.addEventListener(s,c),()=>{const e=O.get(t.current)||[];O.set(t.current,e.filter((e=>e!==o))),window.removeEventListener(s,c)}}),[i]),{state:s,getState:r.default.useCallback((()=>m.get(t.current)||t.current),[]),setState:i,stringify:o}}(c()?function(e,t){return{...t,...b(JSON.stringify(e),t)}}(n,e):e),l=t.useRouter();return{updateUrl:r.default.useCallback(((e,t)=>{a(e??s());const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,o=i("function"==typeof e?e(s()):e??s()),c=`${window.location.pathname}${o.length?"?":""}${o}`;if(r!==c){const{replace:e,...n}=t||{};l[e?"replace":"push"](c,{scroll:!1,...n})}}),[l,i,s]),updateState:a,state:o}};
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},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,d)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),d=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};const g=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return r===f?e:t&&n?r:e};function p(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}function y(t,e,n){const r=c(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([...c(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function S(e){const r=t.useCallback((function(t,r){return"object"===n(t)?y(t,e,r):""}),[e]);return{parse:t.useCallback((function(t){return b(t,e)}),[e]),stringify:r}}const m=new WeakMap,w=new WeakMap;function h(n,c){const{state:s,getState:a,setState:u,stringify:i}=function(e){const n=t.useRef(e),{parse:c,stringify:s}=S(n.current),[a,u]=t.useState((()=>{const t=m.get(n.current),e=c(r()?"":window.location.search);return t||m.set(n.current,e),e})),i=t.useCallback((t=>{const e=m.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(o(e,r))return!1;m.set(n.current,r),(w.get(n.current)||[]).forEach((t=>{t()}))}else{if(o(e,t))return!1;m.set(n.current,t),(w.get(n.current)||[]).forEach((t=>{t()}))}}),[]);return t.useInsertionEffect((()=>{const t=w.get(n.current)||[],r=()=>{u(m.get(n.current)||e)};w.set(n.current,t.concat(r));const o=()=>{const t=c(window.location.search);i(t)},s="popstate";return window.addEventListener(s,o),()=>{const t=w.get(n.current)||[];w.set(n.current,t.filter((t=>t!==r))),window.removeEventListener(s,o)}}),[i]),{state:a,getState:t.useCallback((()=>m.get(n.current)||n.current),[]),setState:i,stringify:s}}(r()?function(t,e){return{...e,...p(JSON.stringify(t),e)}}(c,n):n),l=e();return{updateUrl:t.useCallback(((t,e)=>{u(t??a());const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,c=i("function"==typeof t?t(a()):t??a()),o=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==o){const{replace:t,...n}=e||{};l[t?"replace":"push"](o,{scroll:!1,...n})}}),[l,i,a]),updateState:u,state:s}}export{u as decode,b as decodeState,a as encode,y as encodeState,S as useUrlEncode,h as useUrlState};
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 './utils';
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: never extends T ? object : JSONCompatible<T>, defaults?: JSONCompatible<T>, paramsToKeep?: string | URLSearchParams): string;
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?: JSONCompatible<T>): undefined extends T ? UnknownObj : T;
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): T | Primitive;
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';
@@ -1,7 +1,7 @@
1
- import { encode, decode, parseSsrQs } from './encoder';
2
- import { type Type, type JSONCompatible, typeOf, isSSR } from './utils';
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 { encodeState, decodeState } from './encodeState';
6
- export { encode, decode, typeOf, useUrlEncode, useUrlState, encodeState, decodeState, isSSR, parseSsrQs, };
7
- export type { Type, JSONCompatible };
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 };
@@ -1,7 +1,7 @@
1
- import { type JSONCompatible, type DeepReadonly } from './utils';
2
- export declare function useState<T>(defaultState: JSONCompatible<T>): {
3
- state: JSONCompatible<T>;
4
- getState: () => JSONCompatible<T>;
5
- setState: (value: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>> | ((currState: typeof defaultState | DeepReadonly<typeof defaultState>) => typeof defaultState | DeepReadonly<typeof defaultState>)) => false | undefined;
6
- stringify: (state: JSONCompatible<T> | DeepReadonly<JSONCompatible<T>>, paramsToKeep?: string | URLSearchParams | undefined) => string;
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
- export declare const stateMap: WeakMap<object, object>;
2
- export declare const subscribers: WeakMap<object, Cb[]>;
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 {};
@@ -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 JSONPrimitive = null | boolean | Date | number | string | JSONPrimitive[] | {
13
- [prop: string]: JSONPrimitive;
12
+ export type JSON = null | boolean | Date | number | string | {
13
+ [prop: string]: JSON | JSON[];
14
14
  };
15
- export type JSONCompatible<T> = {
16
- [P in keyof T]: T[P] extends JSONPrimitive ? T[P] : Pick<T, P> extends Required<Pick<T, P>> ? never : JSONCompatible<T[P]>;
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.5",
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.3.1",
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 {};