state-in-url 3.1.0 → 4.0.1
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 +15 -7
- package/dist/next/useUrlState/useUrlState.d.ts +3 -1
- package/dist/next/useUrlState/useUrlState.mjs +1 -1
- package/dist/react-router/useUrlState/useUrlState.d.ts +6 -4
- package/dist/react-router/useUrlState/useUrlState.mjs +1 -1
- package/dist/useUrlStateBase/useUrlStateBase.d.ts +1 -5
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/dist/utils.d.ts +5 -0
- package/dist/utils.mjs +1 -1
- package/package.json +1 -1
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, setState } = 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) => {
|
|
165
|
+
onChange={(ev) => { setState(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)}>
|
|
@@ -183,14 +185,14 @@ function MyComponent() {
|
|
|
183
185
|
export const form: Form = {
|
|
184
186
|
name: '',
|
|
185
187
|
age: undefined,
|
|
186
|
-
|
|
188
|
+
agree_to_terms: false,
|
|
187
189
|
tags: [],
|
|
188
190
|
};
|
|
189
191
|
|
|
190
192
|
type Form = {
|
|
191
193
|
name: string;
|
|
192
194
|
age?: number;
|
|
193
|
-
|
|
195
|
+
agree_to_terms: boolean;
|
|
194
196
|
tags: { id: string; value: { text: string; time: Date } }[];
|
|
195
197
|
};
|
|
196
198
|
```
|
|
@@ -389,14 +391,14 @@ API is same as for Next.js version, except can pass options from [NavigateOption
|
|
|
389
391
|
export const form: Form = {
|
|
390
392
|
name: '',
|
|
391
393
|
age: undefined,
|
|
392
|
-
|
|
394
|
+
agree_to_terms: false,
|
|
393
395
|
tags: [],
|
|
394
396
|
};
|
|
395
397
|
|
|
396
398
|
type Form = {
|
|
397
399
|
name: string;
|
|
398
400
|
age?: number;
|
|
399
|
-
|
|
401
|
+
agree_to_terms: boolean;
|
|
400
402
|
tags: { id: string; value: { text: string; time: Date } }[];
|
|
401
403
|
};
|
|
402
404
|
|
|
@@ -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, setState } = 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) => { setState(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
|
|
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,17 +19,18 @@ 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:
|
|
26
28
|
* ```ts
|
|
27
|
-
*
|
|
29
|
+
* setState({ name: 'test' });
|
|
28
30
|
* // or
|
|
29
|
-
*
|
|
31
|
+
* setState(curr => ({ ...curr, name: 'test' }) );
|
|
30
32
|
* // can pass optional React-Router `NavigateOptions`
|
|
31
|
-
*
|
|
33
|
+
* setState(curr => ({ ...curr, name: 'test', preventScrollReset: false }) );
|
|
32
34
|
* ```
|
|
33
35
|
*
|
|
34
36
|
* * Docs {@link https://github.com/asmyshlyaev177/state-in-url/tree/master/packages/urlstate/react-router/useUrlState#updatestate}
|
|
@@ -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{
|
|
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:
|
|
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),
|
|
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
|
+
"version": "4.0.1",
|
|
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": [
|