state-in-url 3.1.0 → 4.0.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
@@ -146,7 +146,7 @@ import { userState } from './userState';
146
146
  function MyComponent() {
147
147
  // can pass `replace` arg, it's control will `setUrl` will use `rounter.push` or `router.replace`, default replace=true
148
148
  // can pass `searchParams` from server components
149
- const { urlState, setUrl } = useUrlState({ defaultState: userState });
149
+ const { urlState, setUrl, setUrlState } = useUrlState({ defaultState: userState });
150
150
 
151
151
  // won't let you to accidently mutate state directly, requires TS
152
152
  // urlState.name = 'John' // <- error
@@ -162,7 +162,9 @@ function MyComponent() {
162
162
 
163
163
  // same api as React.useState
164
164
  <input value={urlState.name}
165
- onChange={(ev) => { setUrl(curr => ({ ...curr, name: ev.target.value })) }}
165
+ onChange={(ev) => { setUrlState(curr => ({ ...curr, name: ev.target.value })) }}
166
+ // Can update state immediately but sync change to url as needed
167
+ onBlur={() => setUrl()}
166
168
  />
167
169
 
168
170
  <button onClick={() => setUrl(userState)}>
@@ -408,7 +410,7 @@ import { useUrlState } from 'state-in-url/react-router';
408
410
  import { form } from './form';
409
411
 
410
412
  function TagsComponent() {
411
- const { urlState, setUrl } = useUrlState({ defaultState: form });
413
+ const { urlState, setUrl, setUrlState } = useUrlState({ defaultState: form });
412
414
 
413
415
  const onChangeTags = React.useCallback(
414
416
  (tag: (typeof tags)[number]) => {
@@ -436,6 +438,12 @@ function TagsComponent() {
436
438
  ))}
437
439
  </div>
438
440
  </Field>
441
+
442
+ <input value={urlState.name}
443
+ onChange={(ev) => { setUrlState(curr => ({ ...curr, name: ev.target.value })) }}
444
+ // Can update state immediately but sync change to url as needed
445
+ onBlur={() => setUrl()}
446
+ />
439
447
  </div>
440
448
  );
441
449
  }
@@ -5,6 +5,7 @@ import { type DeepReadonly, type JSONCompatible } from "../../utils";
5
5
  *
6
6
  * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
7
7
  * @param {?SearchParams<T>} [searchParams] searchParams from Next server component
8
+ * @param {boolean} [useHistory] use window.history for navigation, no _rsc requests https://github.com/vercel/next.js/discussions/59167
8
9
  *
9
10
  * * Example:
10
11
  * ```ts
@@ -22,11 +23,12 @@ import { type DeepReadonly, type JSONCompatible } from "../../utils";
22
23
  *
23
24
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/next/useUrlState#api}
24
25
  */
25
- export declare function useUrlState<T extends JSONCompatible>({ defaultState, searchParams, ...opts }: {
26
+ export declare function useUrlState<T extends JSONCompatible>({ defaultState, searchParams, useHistory, ...opts }: {
26
27
  defaultState: T;
27
28
  searchParams?: object;
28
29
  replace?: boolean;
29
30
  scroll?: boolean;
31
+ useHistory?: boolean;
30
32
  }): {
31
33
  /**
32
34
  * * Example:
@@ -1 +1 @@
1
- import{useRouter as t,useSearchParams as e}from"next/navigation";import a from"react";import{parseSPObj as r}from"../../parseSPObj.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";import{isSSR as o,filterUnknownParams as m,filterUnknownParamsClient as l}from"../../utils.mjs";function p({defaultState:p,searchParams:c,...i}){const n=t(),{state:f,updateState:S,updateUrl:d,getState:j}=s(p,n,(({parse:t})=>o()?r(m(p,c),p):t(l(p)))),U=a.useCallback(((t,e)=>{const a={...u,...i,...e};d(t,a)}),[d,i]),b=e();return a.useEffect((()=>{S(m(p,r(Object.fromEntries([...b.entries()]),p)))}),[b]),{setState:S,updateState:S,setUrl:U,updateUrl:U,urlState:f,state:f,getState:j}}const u={replace:!0,scroll:!1};export{p 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: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};
@@ -5,6 +5,7 @@ import { type DeepReadonly, type JSONCompatible } from "../../utils";
5
5
  *
6
6
  * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
7
7
  * @param {NavigateOptions} [NavigateOptions] See type from `react-router-dom`
8
+ * @param {boolean} [useHistory] use window.history for navigation
8
9
  * * Example:
9
10
  * ```ts
10
11
  * export const form = { name: '', age: 0 };
@@ -18,8 +19,9 @@ import { type DeepReadonly, type JSONCompatible } from "../../utils";
18
19
  *
19
20
  * * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#api}
20
21
  */
21
- export declare function useUrlState<T extends JSONCompatible>({ defaultState, ...initOpts }: {
22
+ export declare function useUrlState<T extends JSONCompatible>({ defaultState, useHistory, ...initOpts }: {
22
23
  defaultState: T;
24
+ useHistory?: boolean;
23
25
  } & NavigateOptions): {
24
26
  /**
25
27
  * * Example:
@@ -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{filterUnknownParamsClient as o,assignValue as m,filterUnknownParams as p}from"../../utils.mjs";function u({defaultState:u,...c}){const S=e(),f=t.useMemo((()=>({replace:(t,e)=>S(t,{...l,...c,...e}),push:(t,e)=>S(t,{...l,...c,...e})})),[S,c]),{state:i,updateState:n,updateUrl:d,getState:j}=s(u,f,(({parse:t})=>t(o(u)))),U=t.useCallback(((t,e)=>{const r={...l,...c,...e};d(t,r)}),[c]),[b]=r();return t.useEffect((()=>{n(m(u,i,p(u,a(Object.fromEntries([...b.entries()]),u))))}),[b]),{setState:n,updateState:n,setUrl:U,updateUrl:U,urlState:i,state:i,getState:j}}const l={replace:!0,preventScrollReset:!0};export{u 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)=>{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,5 +1,5 @@
1
1
  import { useUrlEncode } from "../useUrlEncode";
2
- import { type DeepReadonly, type JSONCompatible } from "../utils";
2
+ import { type DeepReadonly, type JSONCompatible, type Router } from "../utils";
3
3
  /**
4
4
  * A custom React hook to create custom hooks.
5
5
  *
@@ -35,8 +35,4 @@ interface OptionsObject {
35
35
  export interface Options extends OptionsObject {
36
36
  replace?: boolean;
37
37
  }
38
- type Router = {
39
- push: (href: string, opts: object) => void;
40
- replace: (href: string, opts: object) => void;
41
- };
42
38
  export {};
@@ -1 +1 @@
1
- import t from"react";import{useInsertionEffect as e}from"../useInsertionEffect.mjs";import{useSharedState as n}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as o}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as r}from"../utils.mjs";function s(s,c,i){const{parse:u,stringify:f}=o(s),{state:h,getState:l,setState:m}=n(s,(()=>i?.({parse:u})||s));e((()=>{const t=()=>{const t=u(r(s));m(t)};return window.addEventListener(a,t),()=>{window.removeEventListener(a,t)}}),[m]);const p=t.useRef([]),d=t.useCallback(((t,e)=>{const n="function"==typeof t?t(l()):t?{...l(),...t}:l(),o=f(n,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}(s)),r=`${window.location.pathname}${o.length?"?":""}${o}${window.location.hash}`;if(r===`${window.location.pathname}${window.location.search}${window.location.hash}`)return;let a;m(n);const{replace:i,...u}=e||{};p.current.push([i?"replace":"push",r,u]),1===p.current.length&&queueMicrotask((()=>{for(;p.current.length;){const t=p.current.shift();if(!t)break;t?.[1]!==a?.[1]&&(a=t)}if(!a)return;const[t,e,n]=a;a=void 0,c[t](e,n)}))}),[c,f,l]);return{updateState:m,updateUrl:d,state:h,getState:l}}const a="popstate";export{s as useUrlStateBase};
1
+ import t from"react";import{useInsertionEffect as e}from"../useInsertionEffect.mjs";import{useSharedState as n}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as o}from"../useUrlEncode/useUrlEncode.mjs";import{filterUnknownParamsClient as r}from"../utils.mjs";function s(s,c,i){const{parse:u,stringify:h}=o(s),{state:l,getState:m,setState:p}=n(s,(()=>i?.({parse:u})||s));e((()=>{const t=()=>{const t=u(r(s));p(t)};return window.addEventListener(a,t),()=>{window.removeEventListener(a,t)}}),[p]);const d=t.useRef([]),w=t.useCallback(((t,e)=>{const n="function"==typeof t?t(m()):t?{...m(),...t}:m(),o=h(n,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}(s)),r=`${window.location.pathname}${o.length?"?":""}${o}${window.location.hash}`;if(r===`${window.location.pathname}${window.location.search}${window.location.hash}`)return;let a;p(n);const{replace:i,...u}=e||{};d.current.push([i?"replace":"push",r,u]),1===d.current.length&&queueMicrotask((()=>{for(;d.current.length;){const t=d.current.shift();t&&t?.[1]!==a?.[1]&&(a=t)}const[t,e,n]=a||{};a=void 0,t&&c[t](e,n)}))}),[c,h,m]);return{updateState:p,updateUrl:w,state:l,getState:m}}const a="popstate";export{s as useUrlStateBase};
package/dist/utils.d.ts CHANGED
@@ -26,3 +26,8 @@ export declare const isEqual: (val1: unknown, val2: unknown) => boolean;
26
26
  export declare function filterUnknownParamsClient<T extends object>(shape: T): string;
27
27
  export declare function filterUnknownParams<T extends object>(shape: T, searchParams?: object): T;
28
28
  export declare function assignValue<T extends object>(shape: T, curr: Partial<T>, newVal: Partial<T>): T;
29
+ export interface Router {
30
+ push: (href: string, opts: object) => void;
31
+ replace: (href: string, opts: object) => void;
32
+ }
33
+ export declare const routerHistory: Router;
package/dist/utils.mjs CHANGED
@@ -1 +1 @@
1
- const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),c=t instanceof Date;return(e?"null":c&&"date")||r&&"array"||!e&&!c&&!r&&"object"===n&&"object"||n},n=()=>"undefined"==typeof window,e=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",c=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function o(t){const n=new URLSearchParams;return s(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function i(t,n){return Object.fromEntries(s(t,Object.entries(n||{})))}function s(t,n){const e=Object.keys(t);return n.filter((([t])=>e.includes(t))).map((([t,n])=>[t.replaceAll("+"," "),n]))}function a(t,n,e){const r=Object.assign({},t,n);return Object.entries(t).forEach((([n])=>{const c=n,o=void 0!==e[c];r[c]=o?e[c]:t[c]})),r}export{a as assignValue,i as filterUnknownParams,o as filterUnknownParamsClient,e as getParams,c as isEqual,n as isSSR,t as typeOf};
1
+ const t=t=>{const n=typeof t,e=null===t,r=Array.isArray(t),o=t instanceof Date;return(e?"null":o&&"date")||r&&"array"||!e&&!o&&!r&&"object"===n&&"object"||n},n=()=>"undefined"==typeof window,e=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",o=(t,n)=>JSON.stringify(t)===JSON.stringify(n);function i(t){const n=new URLSearchParams;return s(t,[...new URLSearchParams(window.location.search).entries()]).forEach((([t,e])=>n.set(t,e))),n.toString()}function c(t,n){return Object.fromEntries(s(t,Object.entries(n||{})))}function s(t,n){const e=Object.keys(t);return n.filter((([t])=>e.includes(t))).map((([t,n])=>[t.replaceAll("+"," "),n]))}function a(t,n,e){const r=Object.assign({},t,n);return Object.entries(t).forEach((([n])=>{const o=n,i=void 0!==e[o];r[o]=i?e[o]:t[o]})),r}const l={push:t=>{window&&window.history.pushState(null,"",t)},replace:t=>{window&&window.history.replaceState(null,"",t)}};export{a as assignValue,c as filterUnknownParams,i as filterUnknownParamsClient,e as getParams,o as isEqual,n as isSSR,l as routerHistory,t as typeOf};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
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
6
  "keywords": [