state-in-url 0.1.3 → 0.1.5
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 +23 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/urlstate/encodeState.d.ts +28 -0
- package/dist/urlstate/index.d.ts +2 -1
- package/dist/urlstate/useUrlEncode.d.ts +17 -4
- package/dist/urlstate/useUrlState.d.ts +24 -0
- package/dist/urlstate/utils.d.ts +1 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<div align="center">
|
|
10
|
-
Seamlessly
|
|
10
|
+
Seamlessly store complex React state object in URL query parameters without losing types. Works with Next.js/React.js applications.
|
|
11
11
|
|
|
12
12
|
<h4 align="center">Don't hesitate to open issue if you found a bug</h4>
|
|
13
13
|
|
|
@@ -43,9 +43,10 @@ Add a ⭐️ to support the project!
|
|
|
43
43
|
|
|
44
44
|
## Table of content
|
|
45
45
|
- [Installation](#installation)
|
|
46
|
-
- [
|
|
47
|
-
- [
|
|
48
|
-
- [
|
|
46
|
+
- [`useUrlState` for Next.js](#useurlstate-hook-for-nextjs)
|
|
47
|
+
- [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
|
|
48
|
+
- [`encodeState` and `decodeState` for pure JS usage](#encodestate-and-decodestate-helpers)
|
|
49
|
+
- [Low-level `encode` and `decode` functions](#encode-and-decode-helpers)
|
|
49
50
|
- [Gothas](#gothas)
|
|
50
51
|
- [Contact & Support](#contact--support)
|
|
51
52
|
- [Changelog](#changelog)
|
|
@@ -71,7 +72,7 @@ Go to [localhost:3000](http://localhost:3000)
|
|
|
71
72
|
|
|
72
73
|
## useUrlState hook for Next.js
|
|
73
74
|
|
|
74
|
-
`useUrlState` is a custom React hook for Next.js applications that
|
|
75
|
+
`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.
|
|
75
76
|
|
|
76
77
|
|
|
77
78
|
### Usage examples
|
|
@@ -178,7 +179,7 @@ function SettingsComponent() {
|
|
|
178
179
|
|
|
179
180
|
## useUrlEncode hook for React.js
|
|
180
181
|
|
|
181
|
-
`useUrlEncode` is a custom React hook that provides utility functions for encoding and decoding state to and from URL search parameters. This hook doesn't depend on Nextjs, and will works with any React application.
|
|
182
|
+
`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.
|
|
182
183
|
|
|
183
184
|
Accepts optional `defaultState` argument.
|
|
184
185
|
|
|
@@ -205,6 +206,22 @@ const Component = () => {
|
|
|
205
206
|
}
|
|
206
207
|
```
|
|
207
208
|
|
|
209
|
+
## `encodeState` and `decodeState` helpers
|
|
210
|
+
|
|
211
|
+
`encodeState` let you encode some object with optional `defaults`, and optional existing queryString
|
|
212
|
+
```ts
|
|
213
|
+
export const form = { name: '' };
|
|
214
|
+
...
|
|
215
|
+
encodeState({ name: 'test' }, form, 'someExistingParam=123');
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`decodeState` let you decode queryString with optional defaults
|
|
219
|
+
```ts
|
|
220
|
+
export const form = { name: '' };
|
|
221
|
+
...
|
|
222
|
+
decodeState('name=Alex', form);
|
|
223
|
+
```
|
|
224
|
+
|
|
208
225
|
## `encode` and `decode` helpers
|
|
209
226
|
|
|
210
227
|
There low level helpers to stringify and parse query string params. Useful for other frameworks or pure JS.
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { encode, decode, type Type, useUrlEncode, useUrlState, } from './urlstate';
|
|
1
|
+
export { encodeState, decodeState, encode, decode, type Type, useUrlEncode, useUrlState, } from './urlstate';
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"react";import{useRouter as t,usePathname as n,useSearchParams as r}from"next/navigation";const c=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},a={boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function
|
|
1
|
+
import e from"react";import{useRouter as t,usePathname as n,useSearchParams as r}from"next/navigation";const c=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},a=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||""),o={boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function u(e){switch(c(e)){case"function":case"symbol":return"";case"date":return o.date+e.toISOString();case"string":return encodeURIComponent(e);case"number":return o.number+String(e);case"boolean":return o.boolean+String(e);case"object":case"array":return s(e);case"null":return o.null;case"undefined":return o.undefined;default:return String(e)}}const s=e=>JSON.stringify(e,i).replaceAll('"',"'"),i=(e,t)=>{const n=c(t);return e&&"object"!==n&&"array"!==n?u(t):t},l=e=>e.match(/^∓/)?Number.parseFloat(e.replaceAll("∓","")):e.match(/^🗵/)?!!e.includes("true"):e.match(/^⏲/)?new Date(e.slice(1)):e===o.null?null:e!==o.undefined?decodeURIComponent(e):void 0,f=(e,t)=>e&&"string"==typeof t?l(t):t,p=e=>{let t;try{t=JSON.parse(e,f)}catch{}return t};function d(e){const t=e.replaceAll("'",'"');return p(t)??l(e)}function g(e,t,n){const r=a(n);return Object.entries(e||{}).forEach((([e,n])=>{const c=t?.[e];JSON.stringify(n)!==JSON.stringify(c)&&r.set(e,u(n))})),r.toString()}function y(e,t){return{...t||{},...Object.fromEntries([...a(e).entries()].map((([e,n])=>[e,d(n)??t?.[e]])))}}function b(t){const n=e.useCallback((function(e,n){return"object"===c(e)?g(e,t,n):""}),[t]);return{parse:e.useCallback((function(e){return y(e,t)}),[t]),stringify:n}}function m(c){const a=t(),o=n(),{parse:u,stringify:s}=b(c),i=r(),[l,f]=e.useState((()=>u(i)));e.useEffect((()=>{f((e=>{const t=u(i);return JSON.stringify(e)===JSON.stringify(t)?e:t}))}),[u,i]);const p=e.useCallback((e=>{f("function"==typeof e?t=>e(t):e)}),[]);return{updateUrl:e.useCallback(((e,t)=>{const n=i.toString(),r=`${o}${n.length?"?":""}${n}`,c=s("function"==typeof e?e(l):e),u=`${o}${c.length?"?":""}${c}`;if(r!==u){const{replace:e,...n}=t||{};a[e?"replace":"push"](u,{scroll:!1,...n})}}),[o,a,s,i,l]),updateState:p,state:l}}export{d as decode,y as decodeState,u as encode,g as encodeState,b as useUrlEncode,m as useUrlState};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type JSONCompatible } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Encodes the state object into a URL query string.
|
|
4
|
+
*
|
|
5
|
+
* @param {JSONCompatible<T>} state - The state object to encode.
|
|
6
|
+
* @param {JSONCompatible<T>} [defaults] - Optional default values for the state object.
|
|
7
|
+
* @return {string} The encoded URL query string representing the state object.
|
|
8
|
+
*
|
|
9
|
+
* * Example:
|
|
10
|
+
* ```ts
|
|
11
|
+
* export const form = { name: '' };
|
|
12
|
+
* encodeState({ name: 'test' }, form, 'someExistingParam=123');
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function encodeState<T>(state: never extends T ? object : JSONCompatible<T>, defaults?: JSONCompatible<T>, paramsToKeep?: string | URLSearchParams): string;
|
|
16
|
+
/**
|
|
17
|
+
* Decodes a URI string into an object of type T.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} uriString - The URI string to decode.
|
|
20
|
+
* @return {T} The decoded object of type T.
|
|
21
|
+
*
|
|
22
|
+
* * Example:
|
|
23
|
+
* ```ts
|
|
24
|
+
* export const form = { name: '' };
|
|
25
|
+
* decodeState('key=value&name=Alex', form);
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function decodeState<T>(uriString: string | URLSearchParams, defaults?: never extends T ? object : JSONCompatible<T>): never extends T ? object : T;
|
package/dist/urlstate/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ import { encode, decode } from './encoder';
|
|
|
2
2
|
import { type Type, type JSONCompatible, typeOf } from './utils';
|
|
3
3
|
import { useUrlEncode } from './useUrlEncode';
|
|
4
4
|
import { useUrlState } from './useUrlState';
|
|
5
|
-
|
|
5
|
+
import { encodeState, decodeState } from './encodeState';
|
|
6
|
+
export { encode, decode, typeOf, useUrlEncode, useUrlState, encodeState, decodeState, };
|
|
6
7
|
export type { Type, JSONCompatible };
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { type JSONCompatible, type DeepReadonly } from './utils';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* A hook that returns stringify and parse functions for encoding and decoding state
|
|
4
|
+
* to and from URL search parameters.
|
|
4
5
|
*
|
|
5
|
-
* @
|
|
6
|
+
* @template T - The type of the state shape
|
|
7
|
+
* @param {JSONCompatible<T>} [stateShape] Optional 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
|
+
* ```
|
|
6
19
|
*/
|
|
7
20
|
export declare function useUrlEncode<T>(stateShape?: JSONCompatible<T>): {
|
|
8
|
-
parse: (strOrSearchParams
|
|
9
|
-
stringify: (state: DeepReadonly<NonNullable<typeof stateShape>> | NonNullable<typeof stateShape>,
|
|
21
|
+
parse: (strOrSearchParams: string | URLSearchParams) => Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>;
|
|
22
|
+
stringify: (state: DeepReadonly<NonNullable<typeof stateShape>> | NonNullable<typeof stateShape>, paramsToKeep?: string | URLSearchParams) => string;
|
|
10
23
|
};
|
|
@@ -4,6 +4,30 @@ import { type JSONCompatible } from './utils';
|
|
|
4
4
|
* NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
|
|
5
5
|
*
|
|
6
6
|
* @param {?JSONCompatible<T>} [defaultState] Optional fallback values for state
|
|
7
|
+
*
|
|
8
|
+
* * Example:
|
|
9
|
+
* ```ts
|
|
10
|
+
* export const form = { name: '' };
|
|
11
|
+
* const { state, updateState, updateUrl } = useUrlState(form);
|
|
12
|
+
*
|
|
13
|
+
* updateState({ name: 'test' });
|
|
14
|
+
* // by default it's uses router.push with scroll: false
|
|
15
|
+
* updateUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
16
|
+
* ```
|
|
17
|
+
* * Auto update URL
|
|
18
|
+
* ```ts
|
|
19
|
+
* const timer = React.useRef(0 as unknown as NodeJS.Timeout);
|
|
20
|
+
* React.useEffect(() => {
|
|
21
|
+
* clearTimeout(timer.current);
|
|
22
|
+
* timer.current = setTimeout(() => {
|
|
23
|
+
* updateUrl(state);
|
|
24
|
+
* }, 500);
|
|
25
|
+
*
|
|
26
|
+
* return () => {
|
|
27
|
+
* clearTimeout(timer.current);
|
|
28
|
+
* };
|
|
29
|
+
* }, [state, updateUrl]);
|
|
30
|
+
* ```
|
|
7
31
|
*/
|
|
8
32
|
export declare function useUrlState<T>(defaultState?: JSONCompatible<T>): {
|
|
9
33
|
updateUrl: (value: Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never> | ((currState: Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>) => Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>), options?: Options) => void;
|
package/dist/urlstate/utils.d.ts
CHANGED
|
@@ -19,4 +19,5 @@ export type JSONCompatible<T> = unknown extends T ? never : {
|
|
|
19
19
|
export type DeepReadonly<T> = Readonly<{
|
|
20
20
|
readonly [P in keyof T]: DeepReadonly<T[P]>;
|
|
21
21
|
}>;
|
|
22
|
+
export declare const getParams: (strOrSearchParams?: string | URLSearchParams) => URLSearchParams;
|
|
22
23
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
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": [
|
|
@@ -73,10 +73,13 @@
|
|
|
73
73
|
"dev:lib": "wireit",
|
|
74
74
|
"dev:nextjs": "wireit",
|
|
75
75
|
"bump": "npx standard-version --no-verify",
|
|
76
|
+
"bump:ex": "npm i state-in-url@latest --workspace=packages/example-nextjs",
|
|
76
77
|
"build": "wireit",
|
|
77
78
|
"build:demo": "cd packages/example-nextjs && npx cross-env DEMO=true npm run build",
|
|
79
|
+
"start:demo": "cd packages/example-nextjs && npm run build && npx serve -s ./out",
|
|
78
80
|
"cleanup": "rm -rf dist",
|
|
79
81
|
"setup": "playwright install --with-deps",
|
|
82
|
+
"cleanupDist": "wireit",
|
|
80
83
|
"prepare": "npx husky"
|
|
81
84
|
},
|
|
82
85
|
"wireit": {
|