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 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, sync state to URL, TS-friendly, NextJS compatible.
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
- - 🧠 Sync data between unrelated client components
54
- - 🧮 Store unsaved user forms in URL
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> | ((currState: T) => T)) => void;
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> | ((currState: T) => T) | undefined, options?: Options) => void;
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> | ((currState: T) => T)) => void;
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 u,isEqual as c}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[i,a]=t.useState((()=>{if(u())return o?o?.():f.current;const t=o?.()||r.get(f.current)||f.current,e=r.get(f.current);return c(e,t)||r.set(f.current,t),t})),m=t.useCallback((t=>{const n=r.get(f.current);if("function"==typeof t){const u=t(n);if(c(n,u))return;r.set(f.current,u),e.get(f.current).forEach((t=>{t()}))}else{if(c(n,t))return;r.set(f.current,t),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
+ 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> | ((currState: T) => T)) => void;
24
- updateUrl: (value?: T | DeepReadonly<T> | ((currState: T) => T) | undefined, options?: Options) => void;
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:w,stringify:l}=r(a),{state:u,getState:d,setState:f}=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):w(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=w(window.location.search);f(t)};return window.addEventListener(c,t),()=>{window.removeEventListener(c,t)}}),[f]);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(d()),c=l(s,r)):(s=t??d(),c=l(s,r)),f(s);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,l,d]);return{updateState:f,updateUrl:h,state:u,getState:d}}const c="popstate";export{a as useUrlStateBase};
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.2",
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.1",
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.1",
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",