state-in-url 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/urlstate/index.d.ts +2 -1
- package/dist/urlstate/subscribers.d.ts +6 -2
- package/dist/urlstate/useInsertionEffect.d.ts +2 -0
- package/dist/urlstate/useSharedState/index.d.ts +1 -0
- package/dist/urlstate/useSharedState/useSharedState.d.ts +24 -0
- package/package.json +36 -9
- package/dist/urlstate/state.d.ts +0 -7
package/README.md
CHANGED
|
@@ -42,7 +42,8 @@ Add a ⭐️ to support the project!
|
|
|
42
42
|
|
|
43
43
|
## Why use state-in-url?
|
|
44
44
|
|
|
45
|
-
`state-in-url` Simple state management with URL
|
|
45
|
+
`state-in-url` Simple state management with URL sync. Share complex states between unrelated React components, sync state to URL, TS-friendly, NextJS compatible.
|
|
46
|
+
|
|
46
47
|
|
|
47
48
|
# Use cases
|
|
48
49
|
|
|
@@ -66,6 +67,7 @@ Add a ⭐️ to support the project!
|
|
|
66
67
|
|
|
67
68
|
- [Installation](#installation)
|
|
68
69
|
- [`useUrlState` for Next.js](#useurlstate-hook-for-nextjs)
|
|
70
|
+
- [`useSharedState` for React.js](#usesharedstate-hook-for-reactjs)
|
|
69
71
|
- [`useUrlEncode` for React.js](#useurlencode-hook-for-reactjs)
|
|
70
72
|
- [`encodeState` and `decodeState` for pure JS usage](#encodestate-and-decodestate-helpers)
|
|
71
73
|
- [auto sync state with url](#auto-sync-state)
|
|
@@ -257,6 +259,22 @@ function SettingsComponent() {
|
|
|
257
259
|
}
|
|
258
260
|
```
|
|
259
261
|
|
|
262
|
+
## `useSharedState` hook for React.js
|
|
263
|
+
Hook to share state between any React components, tested with Next.js and Vite.
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
'use client'
|
|
267
|
+
import { useSharedState } from 'state-in-url';
|
|
268
|
+
|
|
269
|
+
export const someState = { name: '' };
|
|
270
|
+
|
|
271
|
+
function SettingsComponent() {
|
|
272
|
+
const { state, setState } = useSharedState(someState);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
[Docs](packages/urlstate/useSharedState/README.md)
|
|
277
|
+
|
|
260
278
|
## `useUrlEncode` hook for React.js
|
|
261
279
|
|
|
262
280
|
[Docs](packages/urlstate/useUrlEncode/README.md)
|
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 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={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"},s=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,u=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||""),a=(e,t)=>JSON.stringify(e)===JSON.stringify(t);function i(e){switch(s(e)){case"function":case"symbol":return"";case"date":return o.date+e.toISOString();case"string":return`${o.string}${encodeURIComponent(e)}`;case"number":return o.number+String(e);case"boolean":return o.boolean+String(e);case"object":case"array":return JSON.stringify(e,f).replaceAll('"',"'");case"null":return o.null;case"undefined":return o.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=s(t);return e&&"object"!==n&&"array"!==n?i(t):t},d=e=>e===o.null?null:e!==o.undefined?e?.startsWith?.(o.number)?Number.parseFloat(e.replace(o.number,"")):e?.startsWith?.(o.boolean)?!!e.includes("true"):e?.startsWith?.(o.date)?new Date(e.slice(1)):e?.startsWith?.(o.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 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=u(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 h(e,t){return{...t||{},...Object.fromEntries([...u(e).entries()].map((([e,n])=>{const r=t?.[e];return[e,l(n,r)??r]})))}}const w=new WeakMap,m=new WeakMap,O={get:e=>m.get(e)||[],add(e,t){const n=this.get(e);return m.set(e,n.concat(t)),()=>this.remove(e,t)},remove(e,t){const n=this.get(e).filter((e=>e!==t));n.length?m.set(e,n):m.delete(e)}},v={get:function(e){return w.get(e)},set:function(e,t){w.set(e,t)}},E=r.default.useInsertionEffect||r.default.useEffect;function j(e){const t=r.default.useCallback((function(t,n){return"object"===s(t)?S(t,e,n):""}),[e]);return{parse:r.default.useCallback((function(t){return h(t,e)}),[e]),stringify:t}}exports.decode=l,exports.decodeState=h,exports.encode=i,exports.encodeState=S,exports.useUrlEncode=j,exports.useUrlState=function(e,n){const{parse:o,stringify:s}=j(e),{state:u,getState:i,setState:l}=function(e,t){const n=r.default.useRef(e),[o,s]=r.default.useState((()=>{if(c())return t?.()||n.current;const e=v.get(n.current);if(!e){const e=t?.()||n.current;return v.set(n.current,e),e}return e})),u=r.default.useCallback((e=>{const t=v.get(n.current)||n.current;if("function"==typeof e){const r=e(t);if(a(t,r))return;v.set(n.current,r),O.get(n.current).forEach((e=>{e()}))}else{if(a(t,e))return;v.set(n.current,e),O.get(n.current).forEach((e=>{e()}))}}),[]);return E((()=>{const e=O.add(n.current,(()=>{s(v.get(n.current)||n.current)}));return()=>{e()}}),[]),{state:o,getState:r.default.useCallback((()=>v.get(n.current)||n.current),[]),setState:u}}(e,(()=>{return c()?(t=n,r=e,{...r,...b(JSON.stringify(t),r)}):o(window.location.search);var t,r})),f=t.useRouter();r.default.useInsertionEffect((()=>{const e=()=>{const e=o(window.location.search);l(e)},t="popstate";return window.addEventListener(t,e),()=>{window.removeEventListener(t,e)}}),[l]);const d=r.default.useCallback(((e,t)=>{const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`;let o,c;"function"==typeof e?(o=e(i()),c=s(o)):(o=e??i(),c=s(o)),l(o);const u=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==u){const{replace:e,...n}=t||{};f[e?"replace":"push"](u,{scroll:!1,...n})}}),[f,s,i]);return{updateState:l,updateUrl:d,state:u}};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
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
|
|
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 u(t){switch(r(t)){case"function":case"symbol":return"";case"date":return n.date+t.toISOString();case"string":return`${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 a(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 n=r(e);return t&&"object"!==n&&"array"!==n?u(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"),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&&a(e?.replaceAll?.("'",'"'));return 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=o(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,u(n))})),r.toString()}function b(t,e){return{...e||{},...Object.fromEntries([...o(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,a(n,r)??r]})))}}const h=new WeakMap,S=new WeakMap,m={get:t=>S.get(t)||[],add(t,e){const n=this.get(t);return S.set(t,n.concat(e)),()=>this.remove(t,e)},remove(t,e){const n=this.get(t).filter((t=>t!==e));n.length?S.set(t,n):S.delete(t)}},w={get:function(t){return h.get(t)},set:function(t,e){h.set(t,e)}},O=t.useInsertionEffect||t.useEffect;function E(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}}function N(n,r){const{parse:o,stringify:u}=E(n),{state:a,getState:i,setState:l}=function(e,n){const r=t.useRef(e),[o,u]=t.useState((()=>{if(c())return n?.()||r.current;const t=w.get(r.current);if(!t){const t=n?.()||r.current;return w.set(r.current,t),t}return t})),a=t.useCallback((t=>{const e=w.get(r.current)||r.current;if("function"==typeof t){const n=t(e);if(s(e,n))return;w.set(r.current,n),m.get(r.current).forEach((t=>{t()}))}else{if(s(e,t))return;w.set(r.current,t),m.get(r.current).forEach((t=>{t()}))}}),[]);return O((()=>{const t=m.add(r.current,(()=>{u(w.get(r.current)||r.current)}));return()=>{t()}}),[]),{state:o,getState:t.useCallback((()=>w.get(r.current)||r.current),[]),setState:a}}(n,(()=>{return c()?(t=r,e=n,{...e,...p(JSON.stringify(t),e)}):o(window.location.search);var t,e})),f=e();t.useInsertionEffect((()=>{const t=()=>{const t=o(window.location.search);l(t)},e="popstate";return window.addEventListener(e,t),()=>{window.removeEventListener(e,t)}}),[l]);const d=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(i()),o=u(c)):(c=t??i(),o=u(c)),l(c);const s=`${window.location.pathname}${o.length?"?":""}${o}`;if(r!==s){const{replace:t,...n}=e||{};f[t?"replace":"push"](s,{scroll:!1,...n})}}),[f,u,i]);return{updateState:l,updateUrl:d,state:a}}export{a as decode,b as decodeState,u as encode,y as encodeState,E as useUrlEncode,N as useUrlState};
|
package/dist/urlstate/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { decode, encode, parseSsrQs } from './encoder';
|
|
2
2
|
import { decodeState, encodeState } from './encodeState';
|
|
3
|
+
import { useSharedState } from './useSharedState';
|
|
3
4
|
import { useUrlEncode } from './useUrlEncode';
|
|
4
5
|
import { useUrlState } from './useUrlState';
|
|
5
6
|
import { isSSR, type JSONCompatible, type Type, typeOf } from './utils';
|
|
6
|
-
export { decode, decodeState, encode, encodeState, isSSR, parseSsrQs, typeOf, useUrlEncode, useUrlState, };
|
|
7
|
+
export { decode, decodeState, encode, encodeState, isSSR, parseSsrQs, typeOf, useSharedState, useUrlEncode, useUrlState, };
|
|
7
8
|
export type { JSONCompatible, Type };
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { type JSONCompatible } from './utils';
|
|
2
|
-
export declare const subscribers:
|
|
3
|
-
|
|
2
|
+
export declare const subscribers: {
|
|
3
|
+
get(obj: JSONCompatible): Cb[];
|
|
4
|
+
add(obj: JSONCompatible, cb: Cb): () => void;
|
|
5
|
+
remove(obj: JSONCompatible, cb: Cb): void;
|
|
6
|
+
};
|
|
4
7
|
export declare const stateMap: {
|
|
5
8
|
get: <T extends JSONCompatible>(objKey: T) => T;
|
|
6
9
|
set: <T_1 extends JSONCompatible>(objKey: T_1, value: T_1) => void;
|
|
7
10
|
};
|
|
11
|
+
export type Cb = () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSharedState } from './useSharedState';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
2
|
+
/**
|
|
3
|
+
* Custom React hook for sharing state between unrelated components.
|
|
4
|
+
*
|
|
5
|
+
* @param {T} defaultState - The default state object
|
|
6
|
+
* @param {() => T} [_getInitial] - Optional function to get initial state
|
|
7
|
+
* @returns {Object} Object containing `state`, `getState`, and `setState` functions
|
|
8
|
+
*
|
|
9
|
+
* * Example:
|
|
10
|
+
* ```ts
|
|
11
|
+
* export const form = { name: '' };
|
|
12
|
+
* const { state, setState } = useSharedState(form);
|
|
13
|
+
*
|
|
14
|
+
* setState({ name: 'test' });
|
|
15
|
+
* // OR
|
|
16
|
+
* setState(curr => ({ ...curr, name: 'test' }))
|
|
17
|
+
* ```
|
|
18
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useSharedState}
|
|
19
|
+
*/
|
|
20
|
+
export declare function useSharedState<T extends JSONCompatible>(defaultState: T, _getInitial?: () => T): {
|
|
21
|
+
state: T;
|
|
22
|
+
getState: () => T;
|
|
23
|
+
setState: (value: T | DeepReadonly<T> | ((currState: T) => T)) => void;
|
|
24
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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": [
|
|
@@ -36,7 +36,8 @@
|
|
|
36
36
|
},
|
|
37
37
|
"workspaces": [
|
|
38
38
|
"packages/urlstate",
|
|
39
|
-
"packages/example-nextjs"
|
|
39
|
+
"packages/example-nextjs",
|
|
40
|
+
"packages/example-react"
|
|
40
41
|
],
|
|
41
42
|
"files": [
|
|
42
43
|
"dist"
|
|
@@ -67,7 +68,7 @@
|
|
|
67
68
|
"types": "./dist/index.d.ts",
|
|
68
69
|
"scripts": {
|
|
69
70
|
"test:unit": "wireit",
|
|
70
|
-
"test:int": "
|
|
71
|
+
"test:int": "wireit",
|
|
71
72
|
"test:int:watch": "npx chokidar-cli 'packages/**/*.ts' 'packages/**/*.tsx' -i '.next' -t 500 -c 'npx playwright test -j 2 --project=chromium'",
|
|
72
73
|
"test": "wireit",
|
|
73
74
|
"tsc": "wireit",
|
|
@@ -75,9 +76,8 @@
|
|
|
75
76
|
"dev:lib": "wireit",
|
|
76
77
|
"dev:nextjs": "wireit",
|
|
77
78
|
"bump": "npx standard-version --no-verify",
|
|
78
|
-
"bump:ex": "npm i state-in-url@latest --workspace=packages/example-nextjs",
|
|
79
79
|
"build": "wireit",
|
|
80
|
-
"build:demo": "
|
|
80
|
+
"build:demo": "wireit",
|
|
81
81
|
"start:demo": "npm run build && cd packages/example-nextjs && npm run build && npm run start",
|
|
82
82
|
"cleanup": "rm -rf dist",
|
|
83
83
|
"setup": "playwright install --with-deps",
|
|
@@ -89,11 +89,15 @@
|
|
|
89
89
|
"test": {
|
|
90
90
|
"dependencies": [
|
|
91
91
|
"tsc",
|
|
92
|
-
"
|
|
93
|
-
"test:unit",
|
|
94
|
-
"dev:nextjs"
|
|
92
|
+
"test:unit"
|
|
95
93
|
],
|
|
96
|
-
"command": "
|
|
94
|
+
"command": "npm run test:int"
|
|
95
|
+
},
|
|
96
|
+
"test:int": {
|
|
97
|
+
"command": "playwright test",
|
|
98
|
+
"dependencies": [
|
|
99
|
+
"example:react"
|
|
100
|
+
]
|
|
97
101
|
},
|
|
98
102
|
"tsc": {
|
|
99
103
|
"command": "npx tsc --noEmit"
|
|
@@ -124,12 +128,35 @@
|
|
|
124
128
|
},
|
|
125
129
|
"dev:nextjs": {
|
|
126
130
|
"command": "cd packages/example-nextjs && npm run dev",
|
|
131
|
+
"dependencies": [
|
|
132
|
+
"dev:react"
|
|
133
|
+
],
|
|
127
134
|
"service": {
|
|
128
135
|
"readyWhen": {
|
|
129
136
|
"lineMatches": "Ready in"
|
|
130
137
|
}
|
|
131
138
|
}
|
|
132
139
|
},
|
|
140
|
+
"build:demo": {
|
|
141
|
+
"dependencies": [
|
|
142
|
+
"example:react"
|
|
143
|
+
],
|
|
144
|
+
"command": "cd packages/example-nextjs && npm run build"
|
|
145
|
+
},
|
|
146
|
+
"dev:react": {
|
|
147
|
+
"command": "cd packages/example-react && npm run dev",
|
|
148
|
+
"service": {
|
|
149
|
+
"readyWhen": {
|
|
150
|
+
"lineMatches": "✓ built in"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"example:react": {
|
|
155
|
+
"command": "cd packages/example-react && npm run build",
|
|
156
|
+
"dependencies": [
|
|
157
|
+
"build"
|
|
158
|
+
]
|
|
159
|
+
},
|
|
133
160
|
"build": {
|
|
134
161
|
"command": "rollup -c rollup.config.js",
|
|
135
162
|
"dependencies": [
|
package/dist/urlstate/state.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type DeepReadonly, type JSONCompatible } from './utils';
|
|
2
|
-
export declare function useState<T extends JSONCompatible>(defaultState: T): {
|
|
3
|
-
state: T;
|
|
4
|
-
getState: () => T;
|
|
5
|
-
setState: (value: T | DeepReadonly<T> | ((currState: T) => T)) => void;
|
|
6
|
-
stringify: (state: T, paramsToKeep?: string | URLSearchParams | undefined) => string;
|
|
7
|
-
};
|