state-in-url 2.0.2 → 2.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 +5 -3
- package/dist/next/useUrlState/useUrlState.d.ts +5 -3
- package/dist/useSharedState/useSharedState.d.ts +2 -2
- package/dist/useSharedState/useSharedState.mjs +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.d.ts +6 -6
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/package.json +10 -4
package/README.md
CHANGED
|
@@ -42,16 +42,18 @@ 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 sync. Share complex states between unrelated React components,
|
|
45
|
+
`state-in-url` Simple state management with optional URL sync. Share complex states between unrelated React components, TS-friendly, NextJS compatible.
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
# Use cases
|
|
49
49
|
|
|
50
|
+
- 🧮 Store unsaved user forms in URL
|
|
50
51
|
- 🙃 Share the state between different components without changing url, good as alternative to signals and other state management tools
|
|
52
|
+
- 🧠 Sync data between unrelated client components
|
|
51
53
|
- 🔗 Shareable URLs with full application state
|
|
52
54
|
- 🔄 Easy state persistence across page reloads
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
|
|
56
|
+
|
|
55
57
|
|
|
56
58
|
# Features
|
|
57
59
|
|
|
@@ -8,7 +8,7 @@ import { type DeepReadonly, type JSONCompatible } from '../../utils';
|
|
|
8
8
|
*
|
|
9
9
|
* * Example:
|
|
10
10
|
* ```ts
|
|
11
|
-
* export const form = { name: '' };
|
|
11
|
+
* export const form = { name: '', age: 0 };
|
|
12
12
|
* const { state, updateState, updateUrl } = useUrlState(form);
|
|
13
13
|
* // for nextjs seerver components
|
|
14
14
|
* // const { state, updateState, updateUrl } = useUrlState(form, searchParams);
|
|
@@ -16,6 +16,8 @@ import { type DeepReadonly, type JSONCompatible } from '../../utils';
|
|
|
16
16
|
* updateState({ name: 'test' });
|
|
17
17
|
* // by default it's uses router.push with scroll: false
|
|
18
18
|
* updateUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
19
|
+
* // similar to React.useState
|
|
20
|
+
* updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
|
|
19
21
|
* ```
|
|
20
22
|
*
|
|
21
23
|
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#api}
|
|
@@ -31,7 +33,7 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, s
|
|
|
31
33
|
*
|
|
32
34
|
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#updatestate}
|
|
33
35
|
*/
|
|
34
|
-
updateState: (value: T | DeepReadonly<T
|
|
36
|
+
updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
35
37
|
/**
|
|
36
38
|
* * Example:
|
|
37
39
|
* ```ts
|
|
@@ -42,7 +44,7 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, s
|
|
|
42
44
|
*
|
|
43
45
|
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#updateurl}
|
|
44
46
|
*/
|
|
45
|
-
updateUrl: (value?: T | DeepReadonly<T
|
|
47
|
+
updateUrl: (value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: Options) => void;
|
|
46
48
|
state: DeepReadonly<DeepReadonly<T>>;
|
|
47
49
|
getState: () => T;
|
|
48
50
|
};
|
|
@@ -8,7 +8,7 @@ import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
|
8
8
|
*
|
|
9
9
|
* * Example:
|
|
10
10
|
* ```ts
|
|
11
|
-
* export const form = { name: '' };
|
|
11
|
+
* export const form = { name: '', age: 0 };
|
|
12
12
|
* const { state, setState } = useSharedState(form);
|
|
13
13
|
*
|
|
14
14
|
* setState({ name: 'test' });
|
|
@@ -20,5 +20,5 @@ import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
|
20
20
|
export declare function useSharedState<T extends JSONCompatible>(defaultState: T, _getInitial?: () => T): {
|
|
21
21
|
state: T;
|
|
22
22
|
getState: () => T;
|
|
23
|
-
setState: (value: T | DeepReadonly<T
|
|
23
|
+
setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
24
24
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{stateMap as r,subscribers as e}from"../subscribers.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{isSSR as
|
|
1
|
+
import t from"react";import{stateMap as r,subscribers as e}from"../subscribers.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{isSSR as c,isEqual as u}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[i,a]=t.useState((()=>{if(c())return o?o?.():f.current;const t=o?.()||r.get(f.current)||f.current,e=r.get(f.current);return u(e,t)||r.set(f.current,t),t})),m=t.useCallback((t=>{const n=r.get(f.current);if("function"==typeof t){const c=t(n);if(u(n,c))return;r.set(f.current,c),e.get(f.current).forEach((t=>{t()}))}else{const c={...n,...t};if(u(n,c))return;r.set(f.current,c),e.get(f.current).forEach((t=>{t()}))}}),[]);n((()=>{const t=e.add(f.current,(()=>{a(r.get(f.current)||f.current)}));return()=>{t()}}),[]);return{state:i,getState:t.useCallback((()=>r.get(f.current)||f.current),[]),setState:m}}export{s as useSharedState};
|
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
2
|
-
type Router = {
|
|
3
|
-
push: (href: string, opts: object) => void;
|
|
4
|
-
replace: (href: string, opts: object) => void;
|
|
5
|
-
};
|
|
6
2
|
/**
|
|
7
3
|
* Base hook to integrate it with different routers
|
|
8
4
|
*
|
|
@@ -20,12 +16,16 @@ type Router = {
|
|
|
20
16
|
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase#api}
|
|
21
17
|
*/
|
|
22
18
|
export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, searchParams?: object): {
|
|
23
|
-
updateState: (value: T | DeepReadonly<T
|
|
24
|
-
updateUrl: (value?: T | DeepReadonly<T
|
|
19
|
+
updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
|
|
20
|
+
updateUrl: (value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: Options) => void;
|
|
25
21
|
state: DeepReadonly<T>;
|
|
26
22
|
getState: () => T;
|
|
27
23
|
};
|
|
28
24
|
interface Options extends Object {
|
|
29
25
|
replace?: boolean;
|
|
30
26
|
}
|
|
27
|
+
type Router = {
|
|
28
|
+
push: (href: string, opts: object) => void;
|
|
29
|
+
replace: (href: string, opts: object) => void;
|
|
30
|
+
};
|
|
31
31
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{parseSsrQs as e}from"../next/parseSsrQs.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{useSharedState as o}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as r}from"../useUrlEncode/useUrlEncode.mjs";import{isSSR as s}from"../utils.mjs";function a(a,i,m){const{parse:
|
|
1
|
+
import t from"react";import{parseSsrQs as e}from"../next/parseSsrQs.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{useSharedState as o}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as r}from"../useUrlEncode/useUrlEncode.mjs";import{isSSR as s}from"../utils.mjs";function a(a,i,m){const{parse:u,stringify:w}=r(a),{state:l,getState:f,setState:d}=o(a,(()=>s()?e(function(t,e){const n=Object.keys(t),o=Object.fromEntries(Object.entries(e||{}).filter((([t])=>n.includes(t))));return o}(a,m),a):u(function(t){const e=new URLSearchParams(window.location.search),n=Object.keys(t),o=new URLSearchParams;return[...e.entries()].filter((([t])=>n.includes(t))).forEach((([t,e])=>o.set(t,e))),o.toString()}(a))));n((()=>{const t=()=>{const t=u(window.location.search);d(t)};return window.addEventListener(c,t),()=>{window.removeEventListener(c,t)}}),[d]);const h=t.useCallback(((t,e)=>{const n=`${window.location.pathname}${window.location.search}${window.location.hash}`,o="function"==typeof t,r=function(t){const e=Object.keys(t),n=window.location.search,o=new URLSearchParams(n),r=new URLSearchParams;return o.forEach(((t,n)=>!e.includes(n)&&r.set(n,t))),r}(a);let s,c;o?(s=t(f()),c=w(s,r),d(s)):(d((e=>{if(t){const n={...e,...t};return s=n,n}return s=e,e})),c=w(s,r));const m=`${window.location.pathname}${c.length?"?":""}${c}${window.location.hash}`;if(n!==m){const{replace:t,...n}=e||{};i[t?"replace":"push"](m,{...n})}}),[i,w,f]);return{updateState:d,updateUrl:h,state:l,getState:f}}const c="popstate";export{a as useUrlStateBase};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.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": [
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"test:unit": "wireit",
|
|
86
86
|
"test:int": "wireit",
|
|
87
87
|
"test:int:ci": "wireit",
|
|
88
|
+
"test:exports": "npx babel-node --extensions .js,.mjs,.ts,.mts --config-file ./babel.config.json -- ./packages/urlstate/exportsTest.ts",
|
|
88
89
|
"test": "wireit",
|
|
89
90
|
"tsc": "wireit",
|
|
90
91
|
"dev": "wireit",
|
|
@@ -184,7 +185,7 @@
|
|
|
184
185
|
]
|
|
185
186
|
},
|
|
186
187
|
"build": {
|
|
187
|
-
"command": "rollup -c rollup.config.js",
|
|
188
|
+
"command": "rollup -c rollup.config.js && npm run test:exports",
|
|
188
189
|
"dependencies": [
|
|
189
190
|
"cleanup"
|
|
190
191
|
]
|
|
@@ -218,9 +219,13 @@
|
|
|
218
219
|
]
|
|
219
220
|
},
|
|
220
221
|
"devDependencies": {
|
|
222
|
+
"@babel/core": "^7.25.2",
|
|
223
|
+
"@babel/node": "^7.25.0",
|
|
224
|
+
"@babel/preset-env": "^7.25.3",
|
|
225
|
+
"@babel/preset-typescript": "^7.24.7",
|
|
221
226
|
"@commitlint/cli": "^19.0.3",
|
|
222
227
|
"@commitlint/config-conventional": "^19.0.3",
|
|
223
|
-
"@playwright/test": "^1.45.
|
|
228
|
+
"@playwright/test": "^1.45.3",
|
|
224
229
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
225
230
|
"@rollup/plugin-terser": "^0.4.4",
|
|
226
231
|
"@rollup/plugin-typescript": "^11.1.6",
|
|
@@ -233,6 +238,7 @@
|
|
|
233
238
|
"@types/node": "^20.11.24",
|
|
234
239
|
"@types/react": "^18.3.3",
|
|
235
240
|
"@types/react-dom": "^18.3.0",
|
|
241
|
+
"babel-plugin-module-resolver": "^5.0.2",
|
|
236
242
|
"commitizen": "^4.3.0",
|
|
237
243
|
"cross-env": "^7.0.3",
|
|
238
244
|
"cz": "^1.8.2",
|
|
@@ -243,7 +249,7 @@
|
|
|
243
249
|
"jest": "^29.7.0",
|
|
244
250
|
"jest-environment-jsdom": "^29.7.0",
|
|
245
251
|
"lint-staged": "^15.2.2",
|
|
246
|
-
"playwright": "^1.45.
|
|
252
|
+
"playwright": "^1.45.3",
|
|
247
253
|
"prettier": "^3.2.5",
|
|
248
254
|
"rollup": "^2.79.1",
|
|
249
255
|
"rollup-plugin-filesize": "^10.0.0",
|