state-in-url 4.0.3 → 4.0.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 CHANGED
@@ -23,6 +23,10 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
23
23
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](https://github.com/asmyshlyaev177/state-in-url/)
24
24
  [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)]([https://github.com/semantic-release/semantic-release](https://github.com/asmyshlyaev177/state-in-url))
25
25
  ![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/state-in-url.svg)
26
+ [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/asmyshlyaev177/state-in-url/badge)](https://scorecard.dev/viewer/?uri=github.com/asmyshlyaev177/state-in-url)
27
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/9679/badge)](https://www.bestpractices.dev/projects/9679)
28
+ <!-- or by embedding this in your HTML:
29
+ <a href="https://www.bestpractices.dev/projects/9679"><img src="https://www.bestpractices.dev/projects/9679/badge"></a> -->
26
30
 
27
31
  </div>
28
32
 
@@ -53,7 +57,7 @@ Share if it useful for you.
53
57
 
54
58
  # Why use `state-in-url`?
55
59
 
56
- Store any user state in query parameters; imagine JSON in a browser URL, while keeping types and structure of data.
60
+ Store any user state in query parameters; imagine JSON in a browser URL, while keeping types and structure of data, e.g. numbers will be decoded as numbers not strings, Date stay a date, objects and arrays supported.
57
61
  Dead simple, fast, and with static Typescript validation. Deep links, aka URL synchronization, made easy.
58
62
 
59
63
  Contains `useUrlState` hook for Next.js and react-router, and helpers for anything else on JS.
@@ -131,7 +135,7 @@ All components that use the same `state` object are automatically synchronized.
131
135
 
132
136
  ## useUrlState hook for Next.js
133
137
 
134
- [Docs](packages/urlstate/next/useUrlState#api)
138
+ [Full API Docs](packages/urlstate/next/useUrlState#api)
135
139
 
136
140
  [React-Router example](#useurlstate-hook-for-react-router)
137
141
 
@@ -146,6 +150,7 @@ All components that use the same `state` object are automatically synchronized.
146
150
  // Only parameters with value different from default will go to the url.
147
151
  export const userState: UserState = { name: '', age: 0 }
148
152
 
153
+ // use `Type` not `Interface`!
149
154
  type UserState = { name: string, age: number }
150
155
  ```
151
156
 
@@ -162,9 +167,6 @@ function MyComponent() {
162
167
  // can pass `searchParams` from server components
163
168
  const { urlState, setUrl, setState } = useUrlState({ defaultState: userState });
164
169
 
165
- // won't let you to accidently mutate state directly, requires TS
166
- // urlState.name = 'John' // <- error
167
-
168
170
  return (
169
171
  <div>
170
172
  <input value={urlState.name}
@@ -397,7 +399,7 @@ function SettingsComponent() {
397
399
 
398
400
  API is same as for Next.js version, except can pass options from [NavigateOptions](https://github.com/remix-run/react-router/blob/bc693ed9f39170bda13b9e1b282fb8e9d5925f66/packages/react-router/lib/context.ts#L99) type.
399
401
 
400
- [Docs](packages/urlstate/react-router/useUrlState#api)
402
+ [API Docs](packages/urlstate/react-router/useUrlState#api)
401
403
 
402
404
  ### Example
403
405
 
@@ -486,7 +488,7 @@ const tags = [
486
488
 
487
489
  Hooks to create your own `useUrlState` hooks with other routers, e.g. react-router or tanstack router.
488
490
 
489
- [Docs](packages/urlstate/useUrlStateBase)
491
+ [API Docs](packages/urlstate/useUrlStateBase)
490
492
 
491
493
  ## `useSharedState` hook for React.js
492
494
 
@@ -503,19 +505,19 @@ function SettingsComponent() {
503
505
  }
504
506
  ```
505
507
 
506
- [Docs](packages/urlstate/useSharedState/README.md)
508
+ [API Docs](packages/urlstate/useSharedState/README.md)
507
509
 
508
510
  ## `useUrlEncode` hook for React.js
509
511
 
510
- [Docs](packages/urlstate/useUrlEncode/README.md)
512
+ [API Docs](packages/urlstate/useUrlEncode/README.md)
511
513
 
512
514
  ## `encodeState` and `decodeState` helpers
513
515
 
514
- [Docs](packages/urlstate/encodeState/README.md)
516
+ [API Docs](packages/urlstate/encodeState/README.md)
515
517
 
516
518
  ## `encode` and `decode` helpers
517
519
 
518
- [Docs](packages/urlstate/encoder/README.md)
520
+ [API Docs](packages/urlstate/encoder/README.md)
519
521
 
520
522
  # Best Practices
521
523
 
@@ -1,5 +1,5 @@
1
1
  import { useRouter } from "next/navigation";
2
- import { type DeepReadonly, type JSONCompatible } from "../../utils";
2
+ import { type JSONCompatible } from "../../utils";
3
3
  /**
4
4
  * NextJS hook. Returns `urlState`, `setState`, and `setUrl` functions
5
5
  *
@@ -40,11 +40,11 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState, se
40
40
  *
41
41
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updatestate}
42
42
  */
43
- setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
43
+ setState: (value: Partial<T> | ((currState: T) => T)) => void;
44
44
  /**
45
45
  * @deprecated use `setState`
46
46
  */
47
- updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
47
+ updateState: (value: Partial<T> | ((currState: T) => T)) => void;
48
48
  /**
49
49
  * * Example:
50
50
  * ```ts
@@ -55,16 +55,19 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState, se
55
55
  *
56
56
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#updateurl}
57
57
  */
58
- setUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
58
+ setUrl: (value?: Parameters<(value?: Partial<T> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
59
59
  /**
60
60
  * @deprecated use `setUrl`
61
61
  */
62
- updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
63
- urlState: DeepReadonly<DeepReadonly<T>>;
62
+ updateUrl: (value?: Parameters<(value?: Partial<T> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: Options) => void;
63
+ /**
64
+ * State object. Don't mutate directly, use `setState` or `setUrl`
65
+ */
66
+ urlState: T;
64
67
  /**
65
68
  * @deprecated use `urlState`
66
69
  */
67
- state: DeepReadonly<DeepReadonly<T>>;
70
+ state: T;
68
71
  getState: () => T;
69
72
  };
70
73
  type Router = ReturnType<typeof useRouter>;
@@ -1 +1 @@
1
- import{useRouter as t,useSearchParams as e}from"next/navigation";import r from"react";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as o,isSSR as m,filterUnknownParams as u,filterUnknownParamsClient as i}from"../../utils.mjs";function l({defaultState:l,searchParams:c,useHistory:n,...f}){const S=void 0===n||n?o:t(),{state:d,updateState:j,updateUrl:U,getState:b}=s(l,S,(({parse:t})=>m()?a(u(l,c),l):t(i(l)))),g=r.useCallback(((t,e)=>{const r={...p,...f,...e};U(t,r)}),[U,f]),v=e();return r.useEffect((()=>{j(u(l,a(Object.fromEntries([...v.entries()]),l)))}),[v]),{setState:j,updateState:j,setUrl:g,updateUrl:g,urlState:d,state:d,getState:b}}const p={replace:!0,scroll:!1};export{l as useUrlState};
1
+ import{useRouter as t,useSearchParams as e}from"next/navigation";import r from"react";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as o,isSSR as m,filterUnknownParams as u,filterUnknownParamsClient as i}from"../../utils.mjs";function l({defaultState:l,searchParams:c,useHistory:f,...n}){const S=void 0===f||f?o:t(),{state:d,updateState:j,updateUrl:U,getState:b}=s(l,S,(({parse:t})=>m()?a(u(l,c),l):t(i(l)))),g=r.useCallback(((t,e)=>U(t,{...p,...n,...e})),[U,n]),v=e();return r.useEffect((()=>{j(u(l,a(Object.fromEntries([...v.entries()]),l)))}),[v]),{setState:j,updateState:j,setUrl:g,updateUrl:g,urlState:d,state:d,getState:b}}const p={replace:!0,scroll:!1};export{l as useUrlState};
@@ -1,5 +1,5 @@
1
1
  import { type NavigateOptions } from "react-router-dom";
2
- import { type DeepReadonly, type JSONCompatible } from "../../utils";
2
+ import { type JSONCompatible } from "../../utils";
3
3
  /**
4
4
  * React-router hook. Returns `urlState`, `setState`, and `setUrl` functions
5
5
  *
@@ -35,11 +35,11 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState, us
35
35
  *
36
36
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
37
37
  */
38
- setState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
38
+ setState: (value: Partial<T> | ((currState: T) => T)) => void;
39
39
  /**
40
40
  * @deprecated use `setState`
41
41
  */
42
- updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
42
+ updateState: (value: Partial<T> | ((currState: T) => T)) => void;
43
43
  /**
44
44
  * * Example:
45
45
  * ```ts
@@ -52,15 +52,18 @@ export declare function useUrlState<T extends JSONCompatible>({ defaultState, us
52
52
  *
53
53
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updateurl}
54
54
  */
55
- setUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
55
+ setUrl: (value?: Parameters<(value?: Partial<T> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
56
56
  /**
57
57
  * @deprecated use `setUrl`
58
58
  */
59
- updateUrl: (value?: Parameters<(value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
60
- urlState: DeepReadonly<DeepReadonly<T>>;
59
+ updateUrl: (value?: Parameters<(value?: Partial<T> | ((currState: T) => T) | undefined, options?: import("../../useUrlStateBase/useUrlStateBase").Options) => void>[0], options?: NavigateOptions) => void;
60
+ /**
61
+ * State object. Don't mutate directly, use `setState` or `setUrl`
62
+ */
63
+ urlState: T;
61
64
  /**
62
65
  * @deprecated use `urlState`
63
66
  */
64
- state: DeepReadonly<DeepReadonly<T>>;
67
+ state: T;
65
68
  getState: () => T;
66
69
  };
@@ -1 +1 @@
1
- import t from"react";import{useNavigate as e,useSearchParams as r}from"react-router-dom";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as o,filterUnknownParamsClient as u,assignValue as m,filterUnknownParams as p}from"../../utils.mjs";function l({defaultState:l,useHistory:S,...f}){const i=e(),n=t.useMemo((()=>S?o:{replace:(t,e)=>i(t,{...c,...f,...e}),push:(t,e)=>i(t,{...c,...f,...e})}),[i,f]),{state:d,updateState:j,updateUrl:U,getState:b}=s(l,n,(({parse:t})=>t(u(l)))),g=t.useCallback(((t,e)=>{const r={...c,...f,...e};U(t,r)}),[f]),[B]=r();return t.useEffect((()=>{j(m(l,d,p(l,a(Object.fromEntries([...B.entries()]),l))))}),[B]),{setState:j,updateState:j,setUrl:g,updateUrl:g,urlState:d,state:d,getState:b}}const c={replace:!0,preventScrollReset:!0};export{l as useUrlState};
1
+ import t from"react";import{useNavigate as e,useSearchParams as r}from"react-router-dom";import{parseSPObj as a}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{routerHistory as o,filterUnknownParamsClient as u,assignValue as m,filterUnknownParams as p}from"../../utils.mjs";function l({defaultState:l,useHistory:S,...f}){const i=e(),n=t.useMemo((()=>S?o:{replace:(t,e)=>i(t,{...c,...f,...e}),push:(t,e)=>i(t,{...c,...f,...e})}),[i,f]),{state:d,updateState:j,updateUrl:U,getState:b}=s(l,n,(({parse:t})=>t(u(l)))),g=t.useCallback(((t,e)=>U(t,{...c,...f,...e})),[f]),[B]=r();return t.useEffect((()=>{j(m(l,d,p(l,a(Object.fromEntries([...B.entries()]),l))))}),[B]),{setState:j,updateState:j,setUrl:g,updateUrl:g,urlState:d,state:d,getState:b}}const c={replace:!0,preventScrollReset:!0};export{l as useUrlState};
@@ -1,4 +1,4 @@
1
- import { type DeepReadonly, type JSONCompatible } from "../utils";
1
+ import { type JSONCompatible } from "../utils";
2
2
  /**
3
3
  * Custom React hook for sharing state between unrelated components.
4
4
  *
@@ -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: (Partial<T> | Partial<DeepReadonly<T>>) | ((currState: T) => T)) => void;
23
+ setState: (value: Partial<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),[a,i]=t.useState((()=>{if(u())return o?o?.():f.current;const t=r.get(f.current);if(t)return t;{const t=o?.()||f.current;return r.set(f.current,t),t}})),m=t.useCallback((t=>{const n=r.get(f.current),u="function"==typeof t?t(n):{...n,...t};c(n,u)||(r.set(f.current,u),e.get(f.current).forEach((t=>{t()})))}),[]);n((()=>{const t=e.add(f.current,(()=>{i(r.get(f.current)||f.current)}));return()=>{t()}}),[]);return{state:a,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 u,isEqual as c}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[a,i]=t.useState((()=>{if(u())return o?o?.():f.current;const t=r.get(f.current);if(t)return t;{const t=o?.()||f.current;return r.set(f.current,t),t}})),m=t.useCallback((t=>{const n=r.get(f.current),u="function"==typeof t?t(n):{...n,...t};c(n,u)||(r.set(f.current,u),e.get(f.current).forEach((t=>t())))}),[]);n((()=>{const t=e.add(f.current,(()=>{i(r.get(f.current)||f.current)}));return()=>{t()}}),[]);return{state:a,getState:t.useCallback((()=>r.get(f.current)||f.current),[]),setState:m}}export{s as useSharedState};
@@ -1 +1 @@
1
- import t from"react";import{encodeState as r,decodeState as e}from"../encodeState/encodeState.mjs";import{typeOf as o}from"../utils.mjs";function n(n){const c=t.useCallback((function(t,e){return"object"===o(t)?r(t,n,e):""}),[n]);return{parse:t.useCallback((function(t){return e(t,n)}),[n]),stringify:c}}export{n as useUrlEncode};
1
+ import t from"react";import{encodeState as e,decodeState as o}from"../encodeState/encodeState.mjs";import{typeOf as r}from"../utils.mjs";function a(a){const c=t.useCallback(((t,o)=>"object"===r(t)?e(t,a,o):""),[a]);return{parse:t.useCallback((t=>o(t,a)),[a]),stringify:c}}export{a as useUrlEncode};
@@ -1,5 +1,5 @@
1
1
  import { useUrlEncode } from "../useUrlEncode";
2
- import { type DeepReadonly, type JSONCompatible, type Router } from "../utils";
2
+ import { type JSONCompatible, type Router } from "../utils";
3
3
  /**
4
4
  * A custom React hook to create custom hooks.
5
5
  *
@@ -24,9 +24,9 @@ import { type DeepReadonly, type JSONCompatible, type Router } from "../utils";
24
24
  export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, getInitialState?: ({ parse, }: {
25
25
  parse: ReturnType<typeof useUrlEncode<T>>["parse"];
26
26
  }) => T): {
27
- updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
28
- updateUrl: (value?: Parameters<(value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void>[0], options?: Options) => void;
29
- state: DeepReadonly<T>;
27
+ updateState: (value: Partial<T> | ((currState: T) => T)) => void;
28
+ updateUrl: (value?: Parameters<(value: Partial<T> | ((currState: T) => T)) => void>[0], options?: Options) => void;
29
+ state: T;
30
30
  getState: () => T;
31
31
  };
32
32
  interface OptionsObject {
package/dist/utils.d.ts CHANGED
@@ -15,9 +15,6 @@ export type JSON = null | boolean | Date | number | string | {
15
15
  export type JSONCompatible = {
16
16
  [prop: string]: JSON | JSON[];
17
17
  };
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
- readonly [K in keyof T]: DeepReadonly<T[K]>;
20
- } : T;
21
18
  export declare const getParams: (strOrSearchParams?: string | URLSearchParams) => URLSearchParams;
22
19
  export type UnknownObj = object | {
23
20
  [key: string]: unknown;
package/package.json CHANGED
@@ -1,32 +1,8 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "Easily share complex state objects between unrelated React components, preserve types and structure, with TS validation. Deep links and url state synchronization wthout any hasssle or boilerplate.",
5
5
  "homepage": "https://state-in-url.dev",
6
- "keywords": [
7
- "front-end",
8
- "state-management",
9
- "state management",
10
- "deep links",
11
- "deep linking",
12
- "url synchronization",
13
- "useUrlState",
14
- "state in url",
15
- "client components communication",
16
- "query string",
17
- "search params",
18
- "query params",
19
- "typescript",
20
- "workflow",
21
- "javascript",
22
- "hooks",
23
- "react.js",
24
- "reactjs",
25
- "react",
26
- "NextJS",
27
- "Next.js",
28
- "nuqs alternative"
29
- ],
30
6
  "repository": {
31
7
  "type": "git",
32
8
  "url": "git+https://github.com/asmyshlyaev177/state-in-url.git"
@@ -127,6 +103,7 @@
127
103
  "reinstall": "npm run cleanup && npm install",
128
104
  "setup": "playwright install --with-deps",
129
105
  "prepack": "npm run build && cp package.json dist/package.json && npm run build:demo",
106
+ "pin-gh-deps": "pin-github-action .github/workflows/tests.yml",
130
107
  "prepare": "npx husky"
131
108
  },
132
109
  "wireit": {
@@ -387,6 +364,7 @@
387
364
  "jest-environment-jsdom": "^29.7.0",
388
365
  "lint-staged": "^15.2.9",
389
366
  "only-allow": "^1.2.1",
367
+ "pin-github-action": "^1.9.1",
390
368
  "playwright": "^1.48.2",
391
369
  "prettier": "^3.2.5",
392
370
  "prettier-plugin-tailwindcss": "^0.6.8",
@@ -402,5 +380,31 @@
402
380
  "wait-on": "^8.0.1",
403
381
  "wireit": "^0.14.9"
404
382
  },
383
+ "keywords": [
384
+ "front-end",
385
+ "state-management",
386
+ "state management",
387
+ "deep links",
388
+ "deep linking",
389
+ "url synchronization",
390
+ "useUrlState",
391
+ "state in url",
392
+ "client components communication",
393
+ "query string",
394
+ "search params",
395
+ "query params",
396
+ "typescript",
397
+ "workflow",
398
+ "javascript",
399
+ "hooks",
400
+ "react.js",
401
+ "reactjs",
402
+ "react",
403
+ "NextJS",
404
+ "Next.js",
405
+ "nuqs alternative",
406
+ "query params parser",
407
+ "query params parsing"
408
+ ],
405
409
  "packageManager": "npm@10.8.2"
406
410
  }