state-in-url 0.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 +226 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/urlstate/constants.d.ts +7 -0
- package/dist/urlstate/encoder.d.ts +14 -0
- package/dist/urlstate/index.d.ts +6 -0
- package/dist/urlstate/useUrlEncode.d.ts +10 -0
- package/dist/urlstate/useUrlState.d.ts +11 -0
- package/dist/urlstate/utils.d.ts +22 -0
- package/package.json +181 -0
package/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
|
|
2
|
+
<h1 align="center">state-in-url</h1>
|
|
3
|
+
|
|
4
|
+
<p align="center">
|
|
5
|
+
<img src="/assets/logo.svg?raw=true" alt="Library logo"/>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<div align="center">
|
|
9
|
+
Seamlessly sync React state with URL query parameters in Next.js/React.js applications. Simplify state management, enhance shareability, and maintain type safety—all through your browser's URL.
|
|
10
|
+
|
|
11
|
+
<hr />
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
[](https://www.linkedin.com/in/asmyshlyaev177/)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
[](https://www.npmjs.com/package/state-in-url)
|
|
18
|
+

|
|
19
|
+
[](https://github.com/asmyshlyaev177/state-in-url/)
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
# Features
|
|
25
|
+
- **Seamless state synchronization with URL query parameters.**
|
|
26
|
+
- **Preserves shape and types of data**
|
|
27
|
+
- **Full TypeScript support for enhanced developer experience.**
|
|
28
|
+
- **Automatic serialization and deserialization of complex objects.**
|
|
29
|
+
- **Separate hooks for Next.js (useUrlState) and React.js (useUrlEncode) usage.**
|
|
30
|
+
- **Efficient updates with minimal re-renders.**
|
|
31
|
+
- **Lightweight, no dependencies.**
|
|
32
|
+
|
|
33
|
+
## Table of content
|
|
34
|
+
- [Installation](#installation)
|
|
35
|
+
- [Usage with Next.js](#useurlstate-hook-for-nextjs)
|
|
36
|
+
- [Usage with React.js](#useurlencode-hook-for-reactjs)
|
|
37
|
+
- [Low-level encode/decode functions](#encode-and-decode-helpers)
|
|
38
|
+
- [Gothas](#gothas)
|
|
39
|
+
- [Contact & Support](#contact--support)
|
|
40
|
+
- [Changelog](#changelog)
|
|
41
|
+
- [License](#license)
|
|
42
|
+
|
|
43
|
+
## installation
|
|
44
|
+
```sh
|
|
45
|
+
# npm
|
|
46
|
+
npm install --save state-in-url
|
|
47
|
+
# yarn
|
|
48
|
+
yarn add state-in-url
|
|
49
|
+
# pnpm
|
|
50
|
+
pnpm add state-in-url
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## useUrlState hook for Next.js
|
|
54
|
+
|
|
55
|
+
`useUrlState` is a custom React hook for Next.js applications that manages state in the URL query string. It allows you to store and retrieve state from the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Usage examples
|
|
59
|
+
|
|
60
|
+
#### Basic
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
'use client'
|
|
64
|
+
import { useUrlState } from 'state-in-url';
|
|
65
|
+
|
|
66
|
+
// State shape should be stored in a constant, don't pass an object directly
|
|
67
|
+
const countState = { count: 0 };
|
|
68
|
+
|
|
69
|
+
function MyComponent() {
|
|
70
|
+
const { state, updateState, updateUrl } = useUrlState(countState);
|
|
71
|
+
|
|
72
|
+
// won't let you to accidently mutate state directly, requires TS
|
|
73
|
+
// state.count = 2 // <- error
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
<p>Count: {state.count}</p>
|
|
78
|
+
|
|
79
|
+
<button onClick={() => updateUrl({ count: state.count + 1 })}>
|
|
80
|
+
Increment (Update URL)
|
|
81
|
+
</button>
|
|
82
|
+
|
|
83
|
+
<button onClick={() => updateState({ count: state.count + 1 })}>
|
|
84
|
+
Increment (Local Only)
|
|
85
|
+
</button>
|
|
86
|
+
<button onClick={() => updateUrl(state)}>
|
|
87
|
+
Sync changes to url
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### With complex state shape
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
'use client'
|
|
98
|
+
import { useUrlState } from 'state-in-url';
|
|
99
|
+
|
|
100
|
+
interface UserSettings {
|
|
101
|
+
theme: 'light' | 'dark';
|
|
102
|
+
fontSize: number;
|
|
103
|
+
notifications: boolean;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const defaultSettings: UserSettings {
|
|
107
|
+
theme: 'light',
|
|
108
|
+
fontSize: 16,
|
|
109
|
+
notifications: true,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function SettingsComponent() {
|
|
113
|
+
// `state` will infer from UserSettings type!
|
|
114
|
+
const { state, updateUrl } = useUrlState(defaultSettings);
|
|
115
|
+
|
|
116
|
+
const toggleTheme = () => {
|
|
117
|
+
updateUrl(current => ({
|
|
118
|
+
...current,
|
|
119
|
+
theme: current.theme === 'light' ? 'dark' : 'light',
|
|
120
|
+
}));
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div>
|
|
125
|
+
<h2>User Settings</h2>
|
|
126
|
+
<p>Theme: {state.theme}</p>
|
|
127
|
+
<p>Font Size: {state.fontSize}px</p>
|
|
128
|
+
<button onClick={toggleTheme}>Toggle Theme</button>
|
|
129
|
+
{/* Other UI elements to update other settings */}
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
...
|
|
134
|
+
|
|
135
|
+
// Other component
|
|
136
|
+
function Component() {
|
|
137
|
+
const { state } = useUrlState(defaultSettings);
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div>
|
|
141
|
+
<p>Notifications is {state.notifications ? 'On' : 'Off'}</p>
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### With arbitrary state shape (not recommended)
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
'use client'
|
|
151
|
+
import { useUrlState } from 'state-in-url';
|
|
152
|
+
|
|
153
|
+
const someObj = {};
|
|
154
|
+
|
|
155
|
+
function SettingsComponent() {
|
|
156
|
+
const { state, updateUrl, updateState } = useUrlState<object>(someObj);
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## useUrlEncode hook for React.js
|
|
161
|
+
|
|
162
|
+
`useUrlEncode` is a custom React hook that provides utility functions for encoding and decoding state to and from URL search parameters. This hook doesn't depend on Nextjs, and will works with any React application.
|
|
163
|
+
|
|
164
|
+
Accepts optional `defaultState` argument.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { useUrlEncode } from 'state-in-url';
|
|
168
|
+
|
|
169
|
+
const Component = () => {
|
|
170
|
+
const { parse, stringify } = useUrlEncode();
|
|
171
|
+
|
|
172
|
+
const str = stringify({ age: 36 }); // age=∓36
|
|
173
|
+
const obj = parse(str); // { age: 36 }
|
|
174
|
+
|
|
175
|
+
const currentParams = parse(window.location.search);
|
|
176
|
+
// OR
|
|
177
|
+
// const obj = parse(new URLSearchParams(window.location.search))
|
|
178
|
+
|
|
179
|
+
const updateSearch = () => {
|
|
180
|
+
const currentParams = new URLSearchParams(window.location.search);
|
|
181
|
+
const newState = { query: 'react hooks', page: 2 };
|
|
182
|
+
const updatedParamsString = stringify(newState, currentParams);
|
|
183
|
+
console.log(updatedParamsString);
|
|
184
|
+
// Output: existing params + query=react%20hooks&page=2
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## `encode` and `decode` helpers
|
|
190
|
+
|
|
191
|
+
There low level helpers to stringify and parse query string params. Useful for other frameworks or pure JS.
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
import { encode, decode } from 'state-in-url';
|
|
195
|
+
|
|
196
|
+
const state = { obj: [1, 2, 3], obj2: true }
|
|
197
|
+
|
|
198
|
+
// to params
|
|
199
|
+
const params = new URLSearchParams();
|
|
200
|
+
Object.entries(state).forEach(([key, value]) => {
|
|
201
|
+
params.set(key, encode(value));
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// from params
|
|
205
|
+
const obj = Object.fromEntries(
|
|
206
|
+
[...params.entries()].map(([key, value]) => [
|
|
207
|
+
key,
|
|
208
|
+
decode(value),
|
|
209
|
+
]),
|
|
210
|
+
)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Gothas
|
|
214
|
+
|
|
215
|
+
1. Can pass only serializable values, `Function`, `BigInt` or `Symbol` won't work, probably things like `ArrayBuffer` neither. But won't throw an error if you do it accidently.
|
|
216
|
+
2. Developed and tested with recent `Next.js` and `Typescript` versions.
|
|
217
|
+
|
|
218
|
+
## Contact & Support
|
|
219
|
+
|
|
220
|
+
- Create a [GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues) for bug reports, feature requests, or questions
|
|
221
|
+
- Add a ⭐️ [star on GitHub](https://github.com/asmyshlyaev177/state-in-url) to support the project!
|
|
222
|
+
|
|
223
|
+
## [Changelog](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
This project is licensed under the [MIT license](https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { encode, decode, type Type, useUrlEncode, useUrlState, } from './urlstate';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"react";import{useRouter as t,usePathname as n,useSearchParams as r}from"next/navigation";const a=e=>{const t=null===e,n=Array.isArray(e),r=e instanceof Date;return(t?"null":r&&"date")||n&&"array"||!t&&!r&&!n&&"object"==typeof e&&"object"||typeof e},c={boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};function o(e){switch(a(e)){case"function":case"symbol":return"";case"date":return c.date+e.toISOString();case"string":return encodeURIComponent(e);case"number":return c.number+String(e);case"boolean":return c.boolean+String(e);case"object":case"array":return s(e);case"null":return c.null;case"undefined":return c.undefined;default:return String(e)}}const s=e=>JSON.stringify(e,u).replaceAll('"',"'"),u=(e,t)=>{const n=a(t);return e&&"object"!==n&&"array"!==n?o(t):t},i=e=>e.match(/^∓/)?Number.parseFloat(e.replaceAll("∓","")):e.match(/^🗵/)?!!e.includes("true"):e.match(/^⏲/)?new Date(e.slice(1)):e===c.null?null:e!==c.undefined?decodeURIComponent(e):void 0,l=(e,t)=>e&&"string"==typeof t?i(t):t,f=e=>{let t;try{t=JSON.parse(e,l)}catch{}return t};function d(e){const t=e.replaceAll("'",'"');return f(t)??i(e)}function p(t){const n=e.useCallback((function(e,n){const r=y(n);return"object"===a(e)&&Object.entries(e).forEach((([e,n])=>{const a=t?.[e];JSON.stringify(n)!==JSON.stringify(a)?r.set(e,o(n)):r.delete(e)})),r.toString()}),[t]);return{parse:e.useCallback((function(e){const n=y(e);return{...t,...Object.fromEntries([...n.entries()].map((([e,n])=>[e,d(n)??t?.[e]])))}}),[t]),stringify:n}}const y=e=>new URLSearchParams("string"==typeof e?e:e?.toString?.()||"");function b(a){const c=t(),o=n(),{parse:s,stringify:u}=p(a),i=r(),[l,f]=e.useState((()=>s(i)));e.useEffect((()=>{f((e=>{const t=s(i);return JSON.stringify(e)===JSON.stringify(t)?e:t}))}),[s,i]);const d=e.useCallback((e=>{f("function"==typeof e?t=>e(t):e)}),[]);return{updateUrl:e.useCallback((e=>{"function"==typeof e?c.push(`${o}?${u(e(l))}`):c.push(`${o}?${u(e)}`)}),[o,c,u,l]),updateState:d,state:l}}export{d as decode,o as encode,p as useUrlEncode,b as useUrlState};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode any JSON serializable value to URL friendly string
|
|
3
|
+
*
|
|
4
|
+
* @param {unknown} payload any value to serialize
|
|
5
|
+
* @returns {string} URLSearchParams compatible value string
|
|
6
|
+
*/
|
|
7
|
+
export declare function encode(payload: unknown): string;
|
|
8
|
+
/**
|
|
9
|
+
* Decode value part of URLSearchParams back to JS value
|
|
10
|
+
*
|
|
11
|
+
* @param {string} payload URLSearchParams compatible value string
|
|
12
|
+
* @returns {unknown} decoded object
|
|
13
|
+
*/
|
|
14
|
+
export declare function decode<T>(payload: string): T;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { encode, decode } from './encoder';
|
|
2
|
+
import { type Type, type JSONCompatible, typeOf } from './utils';
|
|
3
|
+
import { useUrlEncode } from './useUrlEncode';
|
|
4
|
+
import { useUrlState } from './useUrlState';
|
|
5
|
+
export { encode, decode, typeOf, useUrlEncode, useUrlState };
|
|
6
|
+
export type { Type, JSONCompatible };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type JSONCompatible, type DeepReadonly } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* Returns stringify and parse functions
|
|
4
|
+
*
|
|
5
|
+
* @param {?JSONCompatible<T>} [stateShape] Optional fallback values for state
|
|
6
|
+
*/
|
|
7
|
+
export declare function useUrlEncode<T>(stateShape?: JSONCompatible<T>): {
|
|
8
|
+
parse: (strOrSearchParams?: string | URLSearchParams) => Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>;
|
|
9
|
+
stringify: (state: DeepReadonly<NonNullable<typeof stateShape>> | NonNullable<typeof stateShape>, strOrSearchParams?: string | URLSearchParams) => string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type JSONCompatible } from './utils';
|
|
2
|
+
/**
|
|
3
|
+
* NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
|
|
4
|
+
*
|
|
5
|
+
* @param {?JSONCompatible<T>} [defaultState] Optional fallback values for state
|
|
6
|
+
*/
|
|
7
|
+
export declare function useUrlState<T>(defaultState?: JSONCompatible<T>): {
|
|
8
|
+
updateUrl: (value: Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never> | ((currState: Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>) => Readonly<JSONCompatible<T> & {} extends infer T ? { readonly [P in keyof T]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_1 ? { readonly [P_1 in keyof T_1]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_2 ? { readonly [P_2 in keyof T_2]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_3 ? { readonly [P_3 in keyof T_3]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_4 ? { readonly [P_4 in keyof T_4]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_5 ? { readonly [P_5 in keyof T_5]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_6 ? { readonly [P_6 in keyof T_6]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_7 ? { readonly [P_7 in keyof T_7]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_8 ? { readonly [P_8 in keyof T_8]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_9 ? { readonly [P_9 in keyof T_9]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_10 ? { readonly [P_10 in keyof T_10]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>)) => void;
|
|
9
|
+
updateState: (value: Readonly<JSONCompatible<T> & {} extends infer T_11 ? { readonly [P in keyof T_11]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_12 ? { readonly [P_1 in keyof T_12]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_13 ? { readonly [P_2 in keyof T_13]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_14 ? { readonly [P_3 in keyof T_14]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_15 ? { readonly [P_4 in keyof T_15]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_16 ? { readonly [P_5 in keyof T_16]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_17 ? { readonly [P_6 in keyof T_17]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_18 ? { readonly [P_7 in keyof T_18]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_19 ? { readonly [P_8 in keyof T_19]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_20 ? { readonly [P_9 in keyof T_20]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_21 ? { readonly [P_10 in keyof T_21]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never> | ((currState: Readonly<JSONCompatible<T> & {} extends infer T_11 ? { readonly [P in keyof T_11]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_12 ? { readonly [P_1 in keyof T_12]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_13 ? { readonly [P_2 in keyof T_13]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_14 ? { readonly [P_3 in keyof T_14]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_15 ? { readonly [P_4 in keyof T_15]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_16 ? { readonly [P_5 in keyof T_16]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_17 ? { readonly [P_6 in keyof T_17]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_18 ? { readonly [P_7 in keyof T_18]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_19 ? { readonly [P_8 in keyof T_19]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_20 ? { readonly [P_9 in keyof T_20]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_21 ? { readonly [P_10 in keyof T_21]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>) => Readonly<JSONCompatible<T> & {} extends infer T_11 ? { readonly [P in keyof T_11]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_12 ? { readonly [P_1 in keyof T_12]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_13 ? { readonly [P_2 in keyof T_13]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_14 ? { readonly [P_3 in keyof T_14]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_15 ? { readonly [P_4 in keyof T_15]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_16 ? { readonly [P_5 in keyof T_16]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_17 ? { readonly [P_6 in keyof T_17]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_18 ? { readonly [P_7 in keyof T_18]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_19 ? { readonly [P_8 in keyof T_19]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_20 ? { readonly [P_9 in keyof T_20]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_21 ? { readonly [P_10 in keyof T_21]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>)) => void;
|
|
10
|
+
state: Readonly<JSONCompatible<T> & {} extends infer T_22 ? { readonly [P in keyof T_22]: Readonly<(JSONCompatible<T> & {})[P] extends infer T_23 ? { readonly [P_1 in keyof T_23]: Readonly<(JSONCompatible<T> & {})[P][P_1] extends infer T_24 ? { readonly [P_2 in keyof T_24]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2] extends infer T_25 ? { readonly [P_3 in keyof T_25]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3] extends infer T_26 ? { readonly [P_4 in keyof T_26]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4] extends infer T_27 ? { readonly [P_5 in keyof T_27]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5] extends infer T_28 ? { readonly [P_6 in keyof T_28]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6] extends infer T_29 ? { readonly [P_7 in keyof T_29]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7] extends infer T_30 ? { readonly [P_8 in keyof T_30]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8] extends infer T_31 ? { readonly [P_9 in keyof T_31]: Readonly<(JSONCompatible<T> & {})[P][P_1][P_2][P_3][P_4][P_5][P_6][P_7][P_8][P_9] extends infer T_32 ? { readonly [P_10 in keyof T_32]: Readonly<any>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>; } : never>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Type = 'string' | 'date' | 'boolean' | 'number' | 'bigint' | 'undefined' | 'object' | 'null' | 'function' | 'symbol' | 'array';
|
|
2
|
+
/**
|
|
3
|
+
* A better replacement for `typeof`
|
|
4
|
+
*
|
|
5
|
+
* @param {unknown} val value to detect type
|
|
6
|
+
* @returns {Type} Type of val - `string`, `date`, `boolean`,
|
|
7
|
+
* `number`, `bigint`, `undefined`, `null`, `object`,
|
|
8
|
+
* `function`, `symbol`, `array`
|
|
9
|
+
*/
|
|
10
|
+
export declare const typeOf: (val: unknown) => Type;
|
|
11
|
+
type JSONPrimitive = string | number | boolean | Date | null | undefined;
|
|
12
|
+
type JSONValue = JSONPrimitive | JSONValue[] | {
|
|
13
|
+
[key: string]: JSONValue;
|
|
14
|
+
};
|
|
15
|
+
type NotAssignableToJson = bigint | symbol | Function;
|
|
16
|
+
export type JSONCompatible<T> = unknown extends T ? never : {
|
|
17
|
+
[P in keyof T]: T[P] extends JSONValue ? T[P] : T[P] extends NotAssignableToJson ? never : JSONCompatible<T[P]>;
|
|
18
|
+
};
|
|
19
|
+
export type DeepReadonly<T> = Readonly<{
|
|
20
|
+
readonly [P in keyof T]: DeepReadonly<T[P]>;
|
|
21
|
+
}>;
|
|
22
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "state-in-url",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Library to store state in browser URL, includes hook for NextJS, hook for pure React, and low level helpers.",
|
|
5
|
+
"homepage": "https://github.com/asmyshlyaev177/state-in-url#readme",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"state-management",
|
|
8
|
+
"Next.js",
|
|
9
|
+
"client components communication",
|
|
10
|
+
"query string",
|
|
11
|
+
"typescript",
|
|
12
|
+
"react.js",
|
|
13
|
+
"workflow",
|
|
14
|
+
"javascript",
|
|
15
|
+
"hooks",
|
|
16
|
+
"reactjs",
|
|
17
|
+
"react",
|
|
18
|
+
"NextJS"
|
|
19
|
+
],
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/asmyshlyaev177/state-in-url.git"
|
|
23
|
+
},
|
|
24
|
+
"author": {
|
|
25
|
+
"name": "asmyshlyaev177",
|
|
26
|
+
"email": "asmyshlyaev177@gmail.com",
|
|
27
|
+
"url": "https://www.linkedin.com/in/asmyshlyaev177/"
|
|
28
|
+
},
|
|
29
|
+
"license": "ISC",
|
|
30
|
+
"funding": {
|
|
31
|
+
"type": "patreon",
|
|
32
|
+
"url": "https://patreon.com/asmyshlyaev177"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/asmyshlyaev177/state-in-url/issues"
|
|
36
|
+
},
|
|
37
|
+
"workspaces": [
|
|
38
|
+
"packages/urlstate",
|
|
39
|
+
"packages/example-nextjs"
|
|
40
|
+
],
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"main": "dist/index.js",
|
|
45
|
+
"type": "module",
|
|
46
|
+
"exports": {
|
|
47
|
+
"import": {
|
|
48
|
+
"default": "./dist/index.js",
|
|
49
|
+
"types": "./dist/index.d.ts"
|
|
50
|
+
},
|
|
51
|
+
"module": {
|
|
52
|
+
"types": "./dist/index.js",
|
|
53
|
+
"default": "./dist/index.d.ts"
|
|
54
|
+
},
|
|
55
|
+
"default": {
|
|
56
|
+
"types": "./dist/index.js",
|
|
57
|
+
"default": "./dist/index.d.ts"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"types": "./dist/index.d.ts",
|
|
61
|
+
"typesVersions": {
|
|
62
|
+
"*": {
|
|
63
|
+
"*": [
|
|
64
|
+
"dist/types/"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"test:unit": "jest",
|
|
70
|
+
"test:int": "playwright test",
|
|
71
|
+
"dev": "wireit",
|
|
72
|
+
"dev:lib": "wireit",
|
|
73
|
+
"dev:nextjs": "wireit",
|
|
74
|
+
"bump": "standard-version",
|
|
75
|
+
"build": "wireit",
|
|
76
|
+
"cleanup": "rm -rf dist",
|
|
77
|
+
"setup": "playwright install --with-deps",
|
|
78
|
+
"prepare": "husky"
|
|
79
|
+
},
|
|
80
|
+
"wireit": {
|
|
81
|
+
"dev": {
|
|
82
|
+
"dependencies": [
|
|
83
|
+
"dev:lib"
|
|
84
|
+
],
|
|
85
|
+
"service": true,
|
|
86
|
+
"command": "npm run dev:nextjs"
|
|
87
|
+
},
|
|
88
|
+
"dev:lib": {
|
|
89
|
+
"command": "cross-env IS_DEVELOPMENT=true rollup -c rollup.config.js -w",
|
|
90
|
+
"service": {
|
|
91
|
+
"readyWhen": {
|
|
92
|
+
"lineMatches": "created "
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"dependencies": [
|
|
96
|
+
"cleanup"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"dev:nextjs": {
|
|
100
|
+
"command": "cd packages/example-nextjs && npm run dev",
|
|
101
|
+
"service": {
|
|
102
|
+
"readyWhen": {
|
|
103
|
+
"lineMatches": "Compiled /"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"build": {
|
|
108
|
+
"command": "rollup -c rollup.config.js",
|
|
109
|
+
"dependencies": [
|
|
110
|
+
"cleanup"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"browserslist": [
|
|
115
|
+
"last 5 Chrome versions",
|
|
116
|
+
"last 5 ChromeAndroid versions",
|
|
117
|
+
"last 5 Firefox versions",
|
|
118
|
+
"last 5 FirefoxAndroid versions",
|
|
119
|
+
"Firefox ESR",
|
|
120
|
+
"last 3 Safari major versions",
|
|
121
|
+
"last 2 iOS major versions"
|
|
122
|
+
],
|
|
123
|
+
"lint-staged": {
|
|
124
|
+
"*.{js,jsx,ts,tsx}": [
|
|
125
|
+
"eslint --fix"
|
|
126
|
+
],
|
|
127
|
+
"*.{html,css,less,ejs,json}": [
|
|
128
|
+
"prettier --write"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
"config": {
|
|
132
|
+
"commitizen": {
|
|
133
|
+
"path": "cz-conventional-changelog"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"commitlint": {
|
|
137
|
+
"extends": [
|
|
138
|
+
"@commitlint/config-conventional"
|
|
139
|
+
]
|
|
140
|
+
},
|
|
141
|
+
"devDependencies": {
|
|
142
|
+
"@commitlint/cli": "^19.0.3",
|
|
143
|
+
"@commitlint/config-conventional": "^19.0.3",
|
|
144
|
+
"@playwright/test": "^1.42.0",
|
|
145
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
|
146
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
147
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
148
|
+
"@testing-library/dom": "^10.2.0",
|
|
149
|
+
"@testing-library/jest-dom": "^6.4.2",
|
|
150
|
+
"@testing-library/react": "^16.0.0",
|
|
151
|
+
"@testing-library/react-hooks": "^8.0.1",
|
|
152
|
+
"@types/jest": "^29.5.12",
|
|
153
|
+
"@types/node": "^20.11.24",
|
|
154
|
+
"@types/react": "^18.3.3",
|
|
155
|
+
"@types/react-dom": "^18.3.0",
|
|
156
|
+
"commitizen": "^4.3.0",
|
|
157
|
+
"cross-env": "^7.0.3",
|
|
158
|
+
"cz": "^1.8.2",
|
|
159
|
+
"cz-conventional-changelog": "^3.3.0",
|
|
160
|
+
"eslint": "^8.57.0",
|
|
161
|
+
"eslint-plugin-maintainable": "^0.3.1",
|
|
162
|
+
"husky": "^9.0.11",
|
|
163
|
+
"jest": "^29.7.0",
|
|
164
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
165
|
+
"lint-staged": "^15.2.2",
|
|
166
|
+
"playwright": "^1.42.0",
|
|
167
|
+
"prettier": "^3.2.5",
|
|
168
|
+
"rollup": "^2.79.1",
|
|
169
|
+
"rollup-plugin-copy": "^3.5.0",
|
|
170
|
+
"rollup-plugin-filesize": "^10.0.0",
|
|
171
|
+
"rollup-plugin-ignore": "^1.0.10",
|
|
172
|
+
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
173
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
174
|
+
"standard-version": "^9.5.0",
|
|
175
|
+
"ts-jest": "^29.1.5",
|
|
176
|
+
"ts-node": "^10.9.2",
|
|
177
|
+
"typescript": "^5.3.3",
|
|
178
|
+
"wait-on": "^7.2.0",
|
|
179
|
+
"wireit": "^0.14.4"
|
|
180
|
+
}
|
|
181
|
+
}
|