state-in-url 0.2.0 → 0.2.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 +25 -5
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
<h1 align="center">state-in-url</h1>
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
<
|
|
5
|
+
<div align="center">
|
|
6
6
|
<img src="/assets/logo.svg?raw=true" alt="Library logo"/>
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
<div>Seamlessly store complex nested state object in URL query parameters with IDE autotomplete and TS validation.</div>
|
|
9
|
+
</div>
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
<div align="center">
|
|
13
|
+
<hr />
|
|
11
14
|
|
|
12
15
|
[](https://www.linkedin.com/in/asmyshlyaev177/)
|
|
13
16
|
|
|
@@ -19,7 +22,6 @@
|
|
|
19
22
|
</div>
|
|
20
23
|
|
|
21
24
|
<div align="center">
|
|
22
|
-
Seamlessly store complex React state object in URL query parameters without losing types. Works with Next.js/React.js applications.
|
|
23
25
|
|
|
24
26
|
<h4 align="center">Don't hesitate to open issue if you found a bug</h4>
|
|
25
27
|
|
|
@@ -46,12 +48,14 @@ Add a ⭐️ to support the project!
|
|
|
46
48
|
- 🔗 Shareable URLs with full application state
|
|
47
49
|
- 🔄 Easy state persistence across page reloads
|
|
48
50
|
- 🧠 Pass data between unrelated client components
|
|
51
|
+
- 🙃 Can just share the state without changing url
|
|
49
52
|
- 🧮 Store unsaved user forms in URL
|
|
50
53
|
|
|
51
54
|
# Features
|
|
52
55
|
- 🧩 **Simple**: Handles complex objects without extra effort
|
|
53
|
-
- 📘 **Typescript support and type Safety**: Preserves data types and structure, enhances developer experience with strong typing and JSDoc comments
|
|
56
|
+
- 📘 **Typescript support and type Safety**: Preserves data types and structure, enhances developer experience with IDE suggestions, strong typing and JSDoc comments
|
|
54
57
|
- ⚛️ **Framework Flexibility**: Separate hooks for Next.js and React.js applications, and functions for pure JS
|
|
58
|
+
- ⚙ **Well tested**: Unit tests and Playwright tests
|
|
55
59
|
- 🪶 **Lightweight**: Zero dependencies for a smaller footprint
|
|
56
60
|
|
|
57
61
|
|
|
@@ -113,11 +117,13 @@ function MyComponent() {
|
|
|
113
117
|
Increment (Update URL)
|
|
114
118
|
</button>
|
|
115
119
|
|
|
116
|
-
|
|
120
|
+
// same api as React.useState
|
|
121
|
+
<button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
|
|
117
122
|
Increment (Local Only)
|
|
118
123
|
</button>
|
|
119
124
|
<button onClick={() => updateUrl()}>
|
|
120
125
|
Sync changes to url
|
|
126
|
+
// Or don't sync it and just share state
|
|
121
127
|
</button>
|
|
122
128
|
|
|
123
129
|
<button onClick={() => updateUrl(state)}>
|
|
@@ -157,6 +163,20 @@ function SettingsComponent() {
|
|
|
157
163
|
}));
|
|
158
164
|
};
|
|
159
165
|
|
|
166
|
+
// sync state to url when idle
|
|
167
|
+
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
|
|
168
|
+
React.useEffect(() => {
|
|
169
|
+
clearTimeout(timer.current);
|
|
170
|
+
timer.current = setTimeout(() => {
|
|
171
|
+
// will compare state by content not by reference and fire update only for new values
|
|
172
|
+
updateUrl(state);
|
|
173
|
+
}, 500);
|
|
174
|
+
|
|
175
|
+
return () => {
|
|
176
|
+
clearTimeout(timer.current);
|
|
177
|
+
};
|
|
178
|
+
}, [state, updateUrl]);
|
|
179
|
+
|
|
160
180
|
return (
|
|
161
181
|
<div>
|
|
162
182
|
<h2>User Settings</h2>
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),g=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};
|
|
1
|
+
import t from"react";import{useRouter as e}from"next/navigation";const n=t=>{const e=null===t,n=Array.isArray(t),r=t instanceof Date;return(e?"null":r&&"date")||n&&"array"||!e&&!r&&!n&&"object"==typeof t&&"object"||typeof t},r=()=>"undefined"==typeof window,c=t=>new URLSearchParams("string"==typeof t?t:t?.toString?.()||""),o=(t,e)=>JSON.stringify(t)===JSON.stringify(e),s={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function a(t){switch(n(t)){case"function":case"symbol":return"";case"date":return s.date+t.toISOString();case"string":return t.match(/^◖/)?encodeURIComponent(t):`${s.string}${encodeURIComponent(t)}`;case"number":return s.number+String(t);case"boolean":return s.boolean+String(t);case"object":case"array":return JSON.stringify(t,i).replaceAll('"',"'");case"null":return s.null;case"undefined":return s.undefined;default:return String(t)}}function u(t,e){return function(t,e){try{return JSON.parse(t,g)}catch{const n=l(t);return n!==f?n:e}}(t.replaceAll("'",'"'),e)}const i=(t,e)=>{const r=n(e);return t&&"object"!==r&&"array"!==r?a(e):e},l=t=>t===s.null?null:t!==s.undefined?t?.startsWith?.(s.number)?Number.parseFloat(t.replace(s.number,"")):t?.startsWith?.(s.boolean)?!!t.includes("true"):t?.startsWith?.(s.date)?new Date(t.slice(1)):t?.startsWith?.(s.string)?decodeURIComponent(t).replace(/^◖/,""):f:void 0,f=Symbol("isError"),g=(t,e)=>{const n="string"==typeof e,r=n&&l(e);return r===f?e:t&&n?r:e};const p=(t,e)=>{const n="string"==typeof e,r=n&&u(e?.replaceAll?.("'",'"'));return r===f?e:t&&n?r:e};function d(t,e){try{return JSON.parse(t,p)}catch{const n=l(t);return n!==f?n:e}}function y(t,e,n){const r=c(n);return Object.entries(t||{}).forEach((([t,n])=>{const c=e?.[t];JSON.stringify(n)!==JSON.stringify(c)&&r.set(t,a(n))})),r.toString()}function b(t,e){return{...e||{},...Object.fromEntries([...c(t).entries()].map((([t,n])=>{const r=e?.[t];return[t,u(n,r)??r]})))}}function S(e){const r=t.useCallback((function(t,r){return"object"===n(t)?y(t,e,r):""}),[e]);return{parse:t.useCallback((function(t){return b(t,e)}),[e]),stringify:r}}const m=new WeakMap,h=new WeakMap;function w(n,c){const{state:s,getState:a,setState:u,stringify:i}=function(e){const n=t.useRef(e),{parse:c,stringify:s}=S(n.current),[a,u]=t.useState((()=>{const t=m.get(n.current),e=c(r()?"":window.location.search);return t||m.set(n.current,e),e}));return t.useInsertionEffect((()=>{const t=h.get(n.current)||[],r=()=>{u(m.get(n.current)||e)};return h.set(n.current,t.concat(r)),()=>{const t=h.get(n.current)||[];h.set(n.current,t.filter((t=>t!==r)))}}),[]),{state:a,getState:t.useCallback((()=>m.get(n.current)||n.current),[]),setState:t.useCallback((t=>{const e=m.get(n.current)||n.current;if("function"==typeof t){const r=t(e);if(o(e,r))return!1;m.set(n.current,r),(h.get(n.current)||[]).forEach((t=>{t()}))}else{if(o(e,t))return!1;m.set(n.current,t),(h.get(n.current)||[]).forEach((t=>{t()}))}}),[]),parse:c,stringify:s}}(r()?function(t,e){return{...e,...d(JSON.stringify(t),e)}}(c,n):n),l=e();return{updateUrl:t.useCallback(((t,e)=>{const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`,c=i("function"==typeof t?t(a()):a()),o=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==o){const{replace:t,...n}=e||{};l[t?"replace":"push"](o,{scroll:!1,...n})}}),[l,i,a]),updateState:u,state:s}}export{u as decode,b as decodeState,a as encode,y as encodeState,S as useUrlEncode,w as useUrlState};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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": [
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"cleanup": "rm -rf dist",
|
|
81
81
|
"setup": "playwright install --with-deps",
|
|
82
82
|
"cleanupDist": "wireit",
|
|
83
|
-
"prepack": "npm run test:unit && npm run build",
|
|
83
|
+
"prepack": "npm run test:unit && npm run build && npm run build:demo",
|
|
84
84
|
"prepare": "npx husky"
|
|
85
85
|
},
|
|
86
86
|
"wireit": {
|