state-in-url 1.2.0 → 1.3.0-beta.2
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.CN.md +146 -64
- package/README.md +73 -4
- package/dist/constants/constants.mjs +1 -0
- package/dist/encodeState/encodeState.mjs +1 -0
- package/dist/{urlstate/encoder → encoder}/encoder.d.ts +7 -0
- package/dist/encoder/encoder.mjs +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.mjs +1 -1
- package/dist/{urlstate → next}/useUrlState/useUrlState.d.ts +9 -6
- package/dist/next/useUrlState/useUrlState.mjs +1 -0
- package/dist/subscribers.mjs +1 -0
- package/dist/useInsertionEffect.mjs +1 -0
- package/dist/useSharedState/useSharedState.mjs +1 -0
- package/dist/useUrlEncode/useUrlEncode.mjs +1 -0
- package/dist/useUrlStateBase/index.d.ts +1 -0
- package/dist/useUrlStateBase/useUrlStateBase.d.ts +31 -0
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -0
- package/dist/utils.mjs +1 -0
- package/package.json +26 -18
- package/dist/index.cjs +0 -1
- package/dist/urlstate/index.d.ts +0 -8
- /package/dist/{urlstate/constants → constants}/constants.d.ts +0 -0
- /package/dist/{urlstate/constants → constants}/index.d.ts +0 -0
- /package/dist/{urlstate/encodeState → encodeState}/encodeState.d.ts +0 -0
- /package/dist/{urlstate/encodeState → encodeState}/index.d.ts +0 -0
- /package/dist/{urlstate/encoder → encoder}/index.d.ts +0 -0
- /package/dist/{urlstate → next}/useUrlState/index.d.ts +0 -0
- /package/dist/{urlstate/subscribers.d.ts → subscribers.d.ts} +0 -0
- /package/dist/{urlstate/useInsertionEffect.d.ts → useInsertionEffect.d.ts} +0 -0
- /package/dist/{urlstate/useSharedState → useSharedState}/index.d.ts +0 -0
- /package/dist/{urlstate/useSharedState → useSharedState}/useSharedState.d.ts +0 -0
- /package/dist/{urlstate/useUrlEncode → useUrlEncode}/index.d.ts +0 -0
- /package/dist/{urlstate/useUrlEncode → useUrlEncode}/useUrlEncode.d.ts +0 -0
- /package/dist/{urlstate/utils.d.ts → utils.d.ts} +0 -0
package/README.CN.md
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
[English](./README.md) | [한국어](./README.KO.md) | 简体中文
|
|
2
|
-
|
|
3
1
|
<h1 align="center">state-in-url</h1>
|
|
4
2
|
|
|
5
3
|
<div align="center">
|
|
6
4
|
<img src="/assets/logo.svg?raw=true" alt="库标志"/>
|
|
7
5
|
|
|
8
|
-
<div>轻松在不相关的
|
|
6
|
+
<div>轻松在不相关的React组件之间共享状态,具有IDE自动完成和TS验证功能。无需任何麻烦或样板代码。</div>
|
|
9
7
|
</div>
|
|
10
8
|
|
|
11
9
|
<div align="center">
|
|
@@ -20,50 +18,51 @@
|
|
|
20
18
|
|
|
21
19
|
<div align="center">
|
|
22
20
|
|
|
23
|
-
<h4 align="center">如果发现bug
|
|
21
|
+
<h4 align="center">如果发现bug,请随时提出issue</h4>
|
|
24
22
|
|
|
25
23
|

|
|
26
24
|
|
|
27
|
-
<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank"
|
|
25
|
+
<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">演示</a> |
|
|
28
26
|
<a href ="https://codesandbox.io/p/github/asmyshlyaev177/state-in-url/main?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clyk5bd9y00062v6jspcfrkx7%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%257D%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%257D%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clyk5bd9x00022v6jyg71cr9e%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clyk5bd9x00022v6jyg71cr9e%2522%252C%2522activeTabId%2522%253A%2522clyk5bd9w00012v6j83rq3bvo%2522%257D%252C%2522clyk5bd9x00052v6j5r632b12%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00052v6j5r632b12%2522%252C%2522activeTabId%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522SETUP_TASKS%2522%252C%2522id%2522%253A%2522clyk5bdjx000b2v6jfc8ae464%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A2222%252C%2522id%2522%253A%2522clyk5cjbo004d2v6j3u55k74g%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522ENV_SETUP%2522%252C%2522id%2522%253A%2522clyk5h8dp000r2v6j0r7kc7qq%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%252C%2522clyk5bd9x00042v6jsos2y043%2522%253A%257B%2522id%2522%253A%2522clyk5bd9x00042v6jsos2y043%2522%252C%2522activeTabId%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clyk5bd9x00032v6jtfhj316o%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522NEW_TERMINAL%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522dev%2522%252C%2522id%2522%253A%2522clyk5cbuv001q2v6joegrxxv6%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%252C%257B%2522type%2522%253A%2522TASK_LOG%2522%252C%2522taskId%2522%253A%2522cleanupDist%2522%252C%2522id%2522%253A%2522clyk5dgvo005i2v6jj950sxft%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D" target="_blank">Demo on Codesandbox</a>
|
|
29
27
|
|
|
30
28
|
<hr />
|
|
31
29
|
|
|
32
|
-
|
|
30
|
+
添加⭐️来支持项目!
|
|
33
31
|
|
|
34
32
|
<hr />
|
|
35
33
|
|
|
36
34
|
</div>
|
|
37
35
|
|
|
38
|
-
## 为什么使用
|
|
36
|
+
## 为什么使用state-in-url?
|
|
39
37
|
|
|
40
|
-
`state-in-url
|
|
38
|
+
`state-in-url`是一个简单的状态管理工具,可以与URL同步。在不相关的React组件之间共享复杂状态,将状态同步到URL,对TS友好,兼容NextJS。
|
|
41
39
|
|
|
42
40
|
# 使用场景
|
|
43
41
|
|
|
44
|
-
- 🙃 在不改变
|
|
45
|
-
- 🔗 包含完整应用程序状态的可共享
|
|
46
|
-
- 🔄
|
|
47
|
-
- 🧠
|
|
48
|
-
- 🧮 在
|
|
42
|
+
- 🙃 在不改变URL的情况下,在不同组件之间共享状态,是信号和其他状态管理工具的良好替代方案
|
|
43
|
+
- 🔗 包含完整应用程序状态的可共享URL
|
|
44
|
+
- 🔄 在页面重新加载之间轻松持久化状态
|
|
45
|
+
- 🧠 在不相关的客户端组件之间同步数据
|
|
46
|
+
- 🧮 在URL中存储未保存的用户表单
|
|
49
47
|
|
|
50
48
|
# 特性
|
|
51
49
|
|
|
52
|
-
- 🧩
|
|
53
|
-
- 📘 **TypeScript
|
|
54
|
-
- ⚛️
|
|
55
|
-
- ⚙
|
|
56
|
-
- ⚡
|
|
57
|
-
- 🪶
|
|
50
|
+
- 🧩 **简单**:无需提供者、reducers、样板代码或新概念
|
|
51
|
+
- 📘 **TypeScript支持和类型安全**:保留数据类型和结构,通过IDE建议、强类型和JSDoc注释增强开发体验
|
|
52
|
+
- ⚛️ **框架灵活性**:为Next.js和React.js应用程序提供单独的钩子,以及用于纯JS的函数
|
|
53
|
+
- ⚙ **经过充分测试**:单元测试和Playwright测试
|
|
54
|
+
- ⚡ **快速**:最少的重新渲染
|
|
55
|
+
- 🪶 **轻量级**:零依赖,占用空间小
|
|
58
56
|
|
|
59
57
|
## 目录
|
|
60
58
|
|
|
61
59
|
- [安装](#安装)
|
|
62
|
-
- [Next.js
|
|
63
|
-
- [React.js
|
|
64
|
-
- [
|
|
65
|
-
- [
|
|
66
|
-
- [
|
|
60
|
+
- [Next.js的`useUrlState`](#nextjs的useurlstate钩子)
|
|
61
|
+
- [React.js的`useSharedState`](#reactjs的usesharedstate钩子)
|
|
62
|
+
- [React.js的`useUrlEncode`](#reactjs的useurlencode钩子)
|
|
63
|
+
- [纯JS使用的`encodeState`和`decodeState`](#纯js使用的encodestate和decodestate辅助函数)
|
|
64
|
+
- [自动同步状态到URL](#自动同步状态)
|
|
65
|
+
- [低级`encode`和`decode`函数](#encode和decode辅助函数)
|
|
67
66
|
- [最佳实践](#最佳实践)
|
|
68
67
|
- [注意事项](#注意事项)
|
|
69
68
|
- [联系与支持](#联系与支持)
|
|
@@ -84,15 +83,15 @@ yarn add state-in-url
|
|
|
84
83
|
pnpm add state-in-url
|
|
85
84
|
```
|
|
86
85
|
|
|
87
|
-
### 2. 编辑
|
|
86
|
+
### 2. 编辑tsconfig.json
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
在`tsconfig.json`的`compilerOptions`中设置`"moduleResolution": "Node16"`或`"moduleResolution": "NodeNext"`
|
|
90
89
|
|
|
91
|
-
## Next.js
|
|
90
|
+
## Next.js的useUrlState钩子
|
|
92
91
|
|
|
93
|
-
[文档](
|
|
92
|
+
[文档](ackages/urlstate/next/useUrlState#api)
|
|
94
93
|
|
|
95
|
-
`useUrlState
|
|
94
|
+
`useUrlState`是一个为Next.js应用程序设计的自定义React钩子,使客户端组件之间的通信变得简单。它允许你共享任何复杂的状态并将其与URL搜索参数同步,提供了一种在页面重新加载之间持久化状态并通过URL共享应用程序状态的方法。
|
|
96
95
|
|
|
97
96
|
### 使用示例
|
|
98
97
|
|
|
@@ -112,33 +111,33 @@ pnpm add state-in-url
|
|
|
112
111
|
|
|
113
112
|
```typescript
|
|
114
113
|
'use client'
|
|
115
|
-
import { useUrlState } from 'state-in-url';
|
|
114
|
+
import { useUrlState } from 'state-in-url/next';
|
|
116
115
|
|
|
117
116
|
import { countState } from './countState';
|
|
118
117
|
|
|
119
118
|
function MyComponent() {
|
|
120
|
-
// 用于从服务器组件使用
|
|
119
|
+
// 用于从服务器组件使用searchParams
|
|
121
120
|
// 例如 export default async function Home({ searchParams }: { searchParams: object }) {
|
|
122
121
|
// const { state, updateState, updateUrl } = useUrlState(countState, searchParams);
|
|
123
122
|
const { state, updateState, updateUrl } = useUrlState(countState);
|
|
124
123
|
|
|
125
|
-
//
|
|
124
|
+
// 不允许你意外地直接修改状态,需要TS
|
|
126
125
|
// state.count = 2 // <- 错误
|
|
127
126
|
|
|
128
127
|
return (
|
|
129
128
|
<div>
|
|
130
|
-
<p
|
|
129
|
+
<p>计数:{state.count}</p>
|
|
131
130
|
|
|
132
131
|
<button onClick={() => updateUrl({ count: state.count + 1 }), { replace: true }}>
|
|
133
|
-
|
|
132
|
+
增加(更新URL)
|
|
134
133
|
</button>
|
|
135
134
|
|
|
136
|
-
// 与
|
|
135
|
+
// 与React.useState相同的API
|
|
137
136
|
<button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
|
|
138
|
-
|
|
137
|
+
增加(仅本地)
|
|
139
138
|
</button>
|
|
140
139
|
<button onClick={() => updateUrl()}>
|
|
141
|
-
同步更改到
|
|
140
|
+
同步更改到URL
|
|
142
141
|
// 或者不同步,只共享状态
|
|
143
142
|
</button>
|
|
144
143
|
|
|
@@ -168,12 +167,12 @@ export const userSettings: UserSettings {
|
|
|
168
167
|
|
|
169
168
|
```typescript
|
|
170
169
|
'use client'
|
|
171
|
-
import { useUrlState } from 'state-in-url';
|
|
170
|
+
import { useUrlState } from 'state-in-url/next';
|
|
172
171
|
|
|
173
172
|
import { userSettings } from './userSettings';
|
|
174
173
|
|
|
175
174
|
function SettingsComponent() {
|
|
176
|
-
// `state` 将从
|
|
175
|
+
// `state` 将从UserSettings类型推断!
|
|
177
176
|
const { state, updateUrl } = useUrlState(userSettings);
|
|
178
177
|
|
|
179
178
|
const toggleTheme = () => {
|
|
@@ -183,12 +182,12 @@ function SettingsComponent() {
|
|
|
183
182
|
}));
|
|
184
183
|
};
|
|
185
184
|
|
|
186
|
-
// 空闲时同步状态到
|
|
185
|
+
// 空闲时同步状态到URL
|
|
187
186
|
const timer = React.useRef(0 as unknown as NodeJS.Timeout);
|
|
188
187
|
React.useEffect(() => {
|
|
189
188
|
clearTimeout(timer.current);
|
|
190
189
|
timer.current = setTimeout(() => {
|
|
191
|
-
//
|
|
190
|
+
// 将通过内容而不是引用比较状态,只为新值触发更新
|
|
192
191
|
updateUrl(state);
|
|
193
192
|
}, 500);
|
|
194
193
|
|
|
@@ -200,10 +199,10 @@ function SettingsComponent() {
|
|
|
200
199
|
return (
|
|
201
200
|
<div>
|
|
202
201
|
<h2>用户设置</h2>
|
|
203
|
-
<p
|
|
204
|
-
<p
|
|
202
|
+
<p>主题:{state.theme}</p>
|
|
203
|
+
<p>字体大小:{state.fontSize}px</p>
|
|
205
204
|
<button onClick={toggleTheme}>切换主题</button>
|
|
206
|
-
{/*
|
|
205
|
+
{/* 其他UI元素用于更新其他设置 */}
|
|
207
206
|
</div>
|
|
208
207
|
);
|
|
209
208
|
}
|
|
@@ -215,7 +214,7 @@ function Component() {
|
|
|
215
214
|
|
|
216
215
|
return (
|
|
217
216
|
<div>
|
|
218
|
-
<p
|
|
217
|
+
<p>通知 {state.notifications ? '开启' : '关闭'}</p>
|
|
219
218
|
</div>
|
|
220
219
|
)
|
|
221
220
|
}
|
|
@@ -228,7 +227,7 @@ function Component() {
|
|
|
228
227
|
React.useEffect(() => {
|
|
229
228
|
clearTimeout(timer.current);
|
|
230
229
|
timer.current = setTimeout(() => {
|
|
231
|
-
//
|
|
230
|
+
// 将通过内容而不是引用比较状态,只为新值触发更新
|
|
232
231
|
updateUrl(state);
|
|
233
232
|
}, 500);
|
|
234
233
|
|
|
@@ -238,11 +237,76 @@ function Component() {
|
|
|
238
237
|
}, [state, updateUrl]);
|
|
239
238
|
```
|
|
240
239
|
|
|
241
|
-
####
|
|
240
|
+
#### 服务器端渲染示例
|
|
242
241
|
|
|
243
242
|
```typescript
|
|
243
|
+
export default async function Home({ searchParams }: { searchParams: object }) {
|
|
244
|
+
return (
|
|
245
|
+
<Form sp={searchParams} />
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Form.tsx
|
|
244
250
|
'use client'
|
|
245
|
-
import
|
|
251
|
+
import React from 'react';
|
|
252
|
+
import { useUrlState } from 'state-in-url/next';
|
|
253
|
+
import { form } from './form';
|
|
254
|
+
|
|
255
|
+
const Form = ({ sp }: { sp: object }) => {
|
|
256
|
+
const { state, updateState, updateUrl } = useUrlState(form, sp);
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### 在`layout`组件中使用钩子
|
|
261
|
+
|
|
262
|
+
这是一个棘手的部分,因为使用app router的nextjs不允许从服务器端访问searchParams。有一个使用中间件的解决方法,但它并不漂亮,而且可能在nextjs更新后停止工作。
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// 添加到适当的`layout.tsc`
|
|
266
|
+
export const runtime = 'edge';
|
|
267
|
+
|
|
268
|
+
// middleware.ts
|
|
269
|
+
import type { NextRequest } from 'next/server';
|
|
270
|
+
import { NextResponse } from 'next/server';
|
|
271
|
+
|
|
272
|
+
export function middleware(request: NextRequest) {
|
|
273
|
+
const url = request.url?.includes('_next') ? null : request.url;
|
|
274
|
+
const sp = url?.split?.('?')?.[1] || '';
|
|
275
|
+
|
|
276
|
+
const response = NextResponse.next();
|
|
277
|
+
|
|
278
|
+
if (url !== null) {
|
|
279
|
+
response.headers.set('searchParams', sp);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return response;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// 目标layout组件
|
|
286
|
+
import { headers } from 'next/headers';
|
|
287
|
+
import { decodeState } from 'state-in-url/encodeState';
|
|
288
|
+
|
|
289
|
+
export default async function Layout({
|
|
290
|
+
children,
|
|
291
|
+
}: {
|
|
292
|
+
children: React.ReactNode;
|
|
293
|
+
}) {
|
|
294
|
+
const sp = headers().get('searchParams') || '';
|
|
295
|
+
|
|
296
|
+
return (
|
|
297
|
+
<div>
|
|
298
|
+
<Comp1 searchParams={decodeState(sp, stateShape)} />
|
|
299
|
+
{children}
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
#### 任意状态形状示例(不推荐)
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
'use client'
|
|
309
|
+
import { useUrlState } from 'state-in-url/next';
|
|
246
310
|
|
|
247
311
|
const someObj = {};
|
|
248
312
|
|
|
@@ -251,55 +315,73 @@ function SettingsComponent() {
|
|
|
251
315
|
}
|
|
252
316
|
```
|
|
253
317
|
|
|
254
|
-
## React.js
|
|
318
|
+
## React.js的`useSharedState`钩子
|
|
319
|
+
用于在任何React组件之间共享状态的钩子,已在Next.js和Vite中测试。
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
'use client'
|
|
323
|
+
import { useSharedState } from 'state-in-url';
|
|
324
|
+
|
|
325
|
+
export const someState = { name: '' };
|
|
326
|
+
|
|
327
|
+
function SettingsComponent() {
|
|
328
|
+
const { state, setState } = useSharedState(someState);
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
[文档](packages/urlstate/useSharedState/README.md)
|
|
333
|
+
|
|
334
|
+
## React.js的`useUrlEncode`钩子
|
|
255
335
|
|
|
256
336
|
[文档](packages/urlstate/useUrlEncode/README.md)
|
|
257
337
|
|
|
258
|
-
## 纯
|
|
338
|
+
## 纯JS使用的`encodeState`和`decodeState`辅助函数
|
|
259
339
|
|
|
260
340
|
[文档](packages/urlstate/encodeState/README.md)
|
|
261
341
|
|
|
262
|
-
## `encode
|
|
342
|
+
## `encode`和`decode`辅助函数
|
|
263
343
|
|
|
264
344
|
[文档](packages/urlstate/encoder/README.md)
|
|
265
345
|
|
|
266
346
|
## 最佳实践
|
|
267
347
|
|
|
268
|
-
-
|
|
269
|
-
- 使用
|
|
270
|
-
- 避免在
|
|
271
|
-
-
|
|
272
|
-
-
|
|
348
|
+
- 将您的状态形状定义为常量,以确保一致性
|
|
349
|
+
- 使用TypeScript以增强类型安全性和自动完成功能
|
|
350
|
+
- 避免在URL参数中存储敏感信息
|
|
351
|
+
- 使用`updateState`进行频繁更新,使用`updateUrl`同步更改到URL
|
|
352
|
+
- 在Next.js中使用`Suspence`包装客户端组件
|
|
353
|
+
- 使用这个[扩展](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)以获得可读的TS错误
|
|
273
354
|
|
|
274
355
|
## 注意事项
|
|
275
356
|
|
|
276
|
-
1. 只能传递可序列化的值,`Function`、`BigInt
|
|
277
|
-
2. Vercel
|
|
357
|
+
1. 只能传递可序列化的值,`Function`、`BigInt`或`Symbol`不会工作,可能类似`ArrayBuffer`的东西也不行。
|
|
358
|
+
2. Vercel服务器限制头部(查询字符串和其他内容)的大小为**14KB**,所以保持您的URL状态在~5000个单词以下。https://vercel.com/docs/errors/URL_TOO_LONG
|
|
359
|
+
3. 已在使用app router的`next.js` 14中测试,不计划支持pages。
|
|
278
360
|
|
|
279
361
|
## 本地运行
|
|
280
362
|
|
|
281
|
-
|
|
363
|
+
克隆此仓库,运行`npm install`然后
|
|
282
364
|
|
|
283
365
|
```sh
|
|
284
366
|
npm run dev
|
|
285
367
|
```
|
|
286
368
|
|
|
287
|
-
转到
|
|
369
|
+
转到[localhost:3000](http://localhost:3000)
|
|
288
370
|
|
|
289
371
|
## 联系与支持
|
|
290
372
|
|
|
291
|
-
- 创建
|
|
373
|
+
- 创建[GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues)报告bug、提出功能请求或提问
|
|
292
374
|
|
|
293
375
|
## [更新日志](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
|
|
294
376
|
|
|
295
377
|
## 许可证
|
|
296
378
|
|
|
297
|
-
本项目采用
|
|
379
|
+
本项目采用[MIT许可证](https://github.com/asmyshlyaev177/state-in-url/blob/main/LICENSE)。
|
|
298
380
|
|
|
299
381
|
## 灵感来源
|
|
300
382
|
|
|
301
|
-
[在
|
|
383
|
+
[在Vue中使用URL存储状态](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)
|
|
302
384
|
|
|
303
|
-
[在
|
|
385
|
+
[在URL中存储状态](https://antonz.org/storing-state/)
|
|
304
386
|
|
|
305
387
|
[NextJS useSearchParams](https://nextjs.org/docs/app/api-reference/functions/use-search-params)
|
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ In `tsconfig.json` in `compilerOptions` set `"moduleResolution": "Node16"` or `"
|
|
|
98
98
|
|
|
99
99
|
## useUrlState hook for Next.js
|
|
100
100
|
|
|
101
|
-
[Docs](
|
|
101
|
+
[Docs](ackages/urlstate/next/useUrlState#api)
|
|
102
102
|
|
|
103
103
|
`useUrlState` is a custom React hook for Next.js applications that make communication between client components easy. It allows you to share any complex state and sync it with the URL search parameters, providing a way to persist state across page reloads and share application state via URLs.
|
|
104
104
|
|
|
@@ -120,7 +120,7 @@ In `tsconfig.json` in `compilerOptions` set `"moduleResolution": "Node16"` or `"
|
|
|
120
120
|
|
|
121
121
|
```typescript
|
|
122
122
|
'use client'
|
|
123
|
-
import { useUrlState } from 'state-in-url';
|
|
123
|
+
import { useUrlState } from 'state-in-url/next';
|
|
124
124
|
|
|
125
125
|
import { countState } from './countState';
|
|
126
126
|
|
|
@@ -176,7 +176,7 @@ export const userSettings: UserSettings {
|
|
|
176
176
|
|
|
177
177
|
```typescript
|
|
178
178
|
'use client'
|
|
179
|
-
import { useUrlState } from 'state-in-url';
|
|
179
|
+
import { useUrlState } from 'state-in-url/next';
|
|
180
180
|
|
|
181
181
|
import { userSettings } from './userSettings';
|
|
182
182
|
|
|
@@ -246,11 +246,78 @@ function Component() {
|
|
|
246
246
|
}, [state, updateUrl]);
|
|
247
247
|
```
|
|
248
248
|
|
|
249
|
+
#### With server side rendering
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
export default async function Home({ searchParams }: { searchParams: object }) {
|
|
253
|
+
return (
|
|
254
|
+
<Form sp={searchParams} />
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Form.tsx
|
|
259
|
+
'use client'
|
|
260
|
+
import React from 'react';
|
|
261
|
+
import { useUrlState } from 'state-in-url/next';
|
|
262
|
+
import { form } from './form';
|
|
263
|
+
|
|
264
|
+
const Form = ({ sp }: { sp: object }) => {
|
|
265
|
+
const { state, updateState, updateUrl } = useUrlState(form, sp);
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Using hook in `layout` component
|
|
270
|
+
|
|
271
|
+
That a tricky part, since nextjs with app router doesn't allow to access searchParams from server side. There is workaround with using middleware, but it isn't pretty and can stop working after nextjs update.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// add to appropriate `layout.tsc`
|
|
275
|
+
export const runtime = 'edge';
|
|
276
|
+
|
|
277
|
+
// middleware.ts
|
|
278
|
+
import type { NextRequest } from 'next/server';
|
|
279
|
+
import { NextResponse } from 'next/server';
|
|
280
|
+
|
|
281
|
+
export function middleware(request: NextRequest) {
|
|
282
|
+
const url = request.url?.includes('_next') ? null : request.url;
|
|
283
|
+
const sp = url?.split?.('?')?.[1] || '';
|
|
284
|
+
|
|
285
|
+
const response = NextResponse.next();
|
|
286
|
+
|
|
287
|
+
if (url !== null) {
|
|
288
|
+
response.headers.set('searchParams', sp);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return response;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Target layout component
|
|
295
|
+
import { headers } from 'next/headers';
|
|
296
|
+
import { decodeState } from 'state-in-url/encodeState';
|
|
297
|
+
|
|
298
|
+
export default async function Layout({
|
|
299
|
+
children,
|
|
300
|
+
}: {
|
|
301
|
+
children: React.ReactNode;
|
|
302
|
+
}) {
|
|
303
|
+
const sp = headers().get('searchParams') || '';
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<div>
|
|
307
|
+
<Comp1 searchParams={decodeState(sp, stateShape)} />
|
|
308
|
+
{children}
|
|
309
|
+
</div>
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
```
|
|
315
|
+
|
|
249
316
|
#### With arbitrary state shape (not recommended)
|
|
250
317
|
|
|
251
318
|
```typescript
|
|
252
319
|
'use client'
|
|
253
|
-
import { useUrlState } from 'state-in-url';
|
|
320
|
+
import { useUrlState } from 'state-in-url/next';
|
|
254
321
|
|
|
255
322
|
const someObj = {};
|
|
256
323
|
|
|
@@ -293,12 +360,14 @@ function SettingsComponent() {
|
|
|
293
360
|
- Use TypeScript for enhanced type safety and autocomplete
|
|
294
361
|
- Avoid storing sensitive information in URL parameters
|
|
295
362
|
- Use `updateState` for frequent updates and `updateUrl` to sync changes to url
|
|
363
|
+
- Use `Suspence` to wrap client components in Next.js
|
|
296
364
|
- Use this [extension](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors) for readable TS errors
|
|
297
365
|
|
|
298
366
|
## Gothas
|
|
299
367
|
|
|
300
368
|
1. Can pass only serializable values, `Function`, `BigInt` or `Symbol` won't work, probably things like `ArrayBuffer` neither.
|
|
301
369
|
2. Vercel servers limit size of headers (query string and other stuff) to **14KB**, so keep your URL state under ~5000 words. https://vercel.com/docs/errors/URL_TOO_LONG
|
|
370
|
+
3. Tested with `next.js` 14 with app router, no plans to support pages.
|
|
302
371
|
|
|
303
372
|
## Run locally
|
|
304
373
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"};export{n as SYMBOLS};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{encode as t,decode as r}from"../encoder/encoder.mjs";import{getParams as n}from"../utils.mjs";function e(r,e,o){const i=n(o);return Object.entries(r||{}).forEach((([r,n])=>{const o=e?.[r];JSON.stringify(n)!==JSON.stringify(o)&&i.set(r,t(n))})),i.toString()}function o(t,e){return{...e||{},...Object.fromEntries([...n(t).entries()].map((([t,n])=>{const o=e?.[t];return[t,r(n,o)??o]})))}}export{o as decodeState,e as encodeState};
|
|
@@ -33,4 +33,11 @@ export declare const reviver: (key: string, value: unknown) => unknown;
|
|
|
33
33
|
* * Github {@link https://github.com/asmyshlyaev177/state-in-url}
|
|
34
34
|
*/
|
|
35
35
|
export declare function parseJSON<T extends JSONCompatible>(jsonString: string, fallbackValue?: T): T | Primitive | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Parses the Next.js server-side `searchParams` into an object.
|
|
38
|
+
*
|
|
39
|
+
* @param {object | undefined} sp - The server-side query string to parse.
|
|
40
|
+
* @param {T} defaults - The default values to use if parsing fails.
|
|
41
|
+
* @return {T} - The parsed object or the default values.
|
|
42
|
+
*/
|
|
36
43
|
export declare function parseSsrQs<T extends JSONCompatible>(sp: object | undefined, defaults: T): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SYMBOLS as t}from"../constants/constants.mjs";import{typeOf as r}from"../utils.mjs";function n(n){switch(r(n)){case"function":case"symbol":return"";case"date":return t.date+n.toISOString();case"string":return`${t.string}${encodeURIComponent(n)}`;case"number":return t.number+String(n);case"boolean":return t.boolean+String(n);case"object":case"array":return JSON.stringify(n,s).replaceAll('"',"'");case"null":return t.null;case"undefined":return t.undefined;default:return String(n)}}function e(t,r){return a(t.replaceAll("'",'"'),r)}const s=(t,e)=>{const s=r(e);return t&&"object"!==s&&"array"!==s?n(e):e},o=r=>r===t.null?null:r!==t.undefined?r?.startsWith?.(t.number)?Number.parseFloat(r.replace(t.number,"")):r?.startsWith?.(t.boolean)?!!r.includes("true"):r?.startsWith?.(t.date)?new Date(r.slice(1)):r?.startsWith?.(t.string)?decodeURIComponent(r).replace(/^◖/,""):c:void 0,c=Symbol("isError"),u=(t,r)=>{const n="string"==typeof r,e=n&&o(r);return e===c?r:t&&n?e:r};function a(t,r){try{return JSON.parse(t,u)}catch{const n=o(t);return n!==c?n:r}}function i(t,r){return{...r,...f(JSON.stringify(t),r)}}const l=(t,r)=>{const n="string"==typeof r,s=n&&e(r?.replaceAll?.("'",'"'));return t&&n?s:r};function f(t,r){try{return JSON.parse(t,l)}catch{const n=o(t);return n!==c?n:r}}export{e as decode,o as decodePrimitive,n as encode,c as errorSym,a as parseJSON,i as parseSsrQs,s as replacer,u as reviver};
|
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { decode, encode, parseSsrQs } from './encoder';
|
|
2
|
+
import { decodeState, encodeState } from './encodeState';
|
|
3
|
+
import { useUrlState } from './next/useUrlState';
|
|
4
|
+
import { useSharedState } from './useSharedState';
|
|
5
|
+
import { useUrlEncode } from './useUrlEncode';
|
|
6
|
+
import { useUrlStateBase } from './useUrlStateBase';
|
|
7
|
+
import { isSSR, type JSONCompatible, type Type, typeOf } from './utils';
|
|
8
|
+
export { decode, decodeState, encode, encodeState, isSSR, parseSsrQs, typeOf, useSharedState, useUrlEncode, useUrlState, useUrlStateBase, };
|
|
9
|
+
export type { JSONCompatible, Type };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export{decode,encode,parseSsrQs}from"./encoder/encoder.mjs";export{decodeState,encodeState}from"./encodeState/encodeState.mjs";export{useUrlState}from"./next/useUrlState/useUrlState.mjs";export{useSharedState}from"./useSharedState/useSharedState.mjs";export{useUrlEncode}from"./useUrlEncode/useUrlEncode.mjs";export{useUrlStateBase}from"./useUrlStateBase/useUrlStateBase.mjs";export{isSSR,typeOf}from"./utils.mjs";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useRouter } from 'next/navigation';
|
|
2
|
-
import { type DeepReadonly, type JSONCompatible } from '
|
|
2
|
+
import { type DeepReadonly, type JSONCompatible } from '../../utils';
|
|
3
3
|
/**
|
|
4
4
|
* NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
|
|
5
5
|
*
|
|
@@ -10,13 +10,15 @@ import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
|
10
10
|
* ```ts
|
|
11
11
|
* export const form = { name: '' };
|
|
12
12
|
* const { state, updateState, updateUrl } = useUrlState(form);
|
|
13
|
+
* // for nextjs seerver components
|
|
14
|
+
* // const { state, updateState, updateUrl } = useUrlState(form, searchParams);
|
|
13
15
|
*
|
|
14
16
|
* updateState({ name: 'test' });
|
|
15
17
|
* // by default it's uses router.push with scroll: false
|
|
16
18
|
* updateUrl({ name: 'test' }, { replace: true, scroll: true });
|
|
17
19
|
* ```
|
|
18
20
|
*
|
|
19
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlState#api}
|
|
21
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#api}
|
|
20
22
|
*/
|
|
21
23
|
export declare function useUrlState<T extends JSONCompatible>(defaultState: T, searchParams?: object): {
|
|
22
24
|
/**
|
|
@@ -27,7 +29,7 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, s
|
|
|
27
29
|
* updateState(curr => ({ ...curr, name: 'test' }) );
|
|
28
30
|
* ```
|
|
29
31
|
*
|
|
30
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlState#updatestate}
|
|
32
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#updatestate}
|
|
31
33
|
*/
|
|
32
34
|
updateState: (value: T | DeepReadonly<T> | ((currState: T) => T)) => void;
|
|
33
35
|
/**
|
|
@@ -38,13 +40,14 @@ export declare function useUrlState<T extends JSONCompatible>(defaultState: T, s
|
|
|
38
40
|
* updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
|
|
39
41
|
* ```
|
|
40
42
|
*
|
|
41
|
-
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/useUrlState#updateurl}
|
|
43
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#updateurl}
|
|
42
44
|
*/
|
|
43
45
|
updateUrl: (value?: T | DeepReadonly<T> | ((currState: T) => T) | undefined, options?: Options) => void;
|
|
44
|
-
state: DeepReadonly<T
|
|
46
|
+
state: DeepReadonly<DeepReadonly<T>>;
|
|
47
|
+
getState: () => T;
|
|
45
48
|
};
|
|
46
49
|
type Router = ReturnType<typeof useRouter>;
|
|
47
|
-
type RouterOptions = NonNullable<Parameters<Router['push']>[1]>;
|
|
50
|
+
type RouterOptions = NonNullable<Parameters<Router['push']>[1] | Parameters<Router['replace']>[1]>;
|
|
48
51
|
interface Options extends RouterOptions {
|
|
49
52
|
replace?: boolean;
|
|
50
53
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{useRouter as t,useSearchParams as e}from"next/navigation";import r from"react";import{parseSsrQs as a}from"../../encoder/encoder.mjs";import{useUrlStateBase as o}from"../../useUrlStateBase/useUrlStateBase.mjs";function s(s,m){const n=t(),{state:u,updateState:c,updateUrl:i,getState:p}=o(s,n,m),f=r.useCallback(((t,e)=>{i(t,{scroll:!1,...e})}),[i]),l=e();return r.useEffect((()=>{c(a(Object.fromEntries([...l.entries()]),s))}),[l]),{updateState:c,updateUrl:f,state:u,getState:p}}export{s as useUrlState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=new WeakMap,t=new WeakMap,n={get:e=>t.get(e)||[],add(e,n){const s=this.get(e);return t.set(e,s.concat(n)),()=>this.remove(e,n)},remove(e,n){const s=this.get(e).filter((e=>e!==n));s.length?t.set(e,s):t.delete(e)}},s={get:function(t){return e.get(t)},set:function(t,n){e.set(t,n)}};export{s as stateMap,n as subscribers};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import e from"react";const t=e.useInsertionEffect||e.useEffect;export{t as useInsertionEffect};
|
|
@@ -0,0 +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),[i,a]=t.useState((()=>{if(u())return o?o?.():f.current;const t=o?.()||r.get(f.current)||f.current,e=r.get(f.current);return c(e,t)||r.set(f.current,t),t})),m=t.useCallback((t=>{const n=r.get(f.current);if("function"==typeof t){const u=t(n);if(c(n,u))return;r.set(f.current,u),e.get(f.current).forEach((t=>{t()}))}else{if(c(n,t))return;r.set(f.current,t),e.get(f.current).forEach((t=>{t()}))}}),[]);n((()=>{const t=e.add(f.current,(()=>{a(r.get(f.current)||f.current)}));return()=>{t()}}),[]);return{state:i,getState:t.useCallback((()=>r.get(f.current)||f.current),[]),setState:m}}export{s as useSharedState};
|
|
@@ -0,0 +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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useUrlStateBase';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type DeepReadonly, type JSONCompatible } from '../utils';
|
|
2
|
+
type Router = {
|
|
3
|
+
push: (href: string, opts: object) => void;
|
|
4
|
+
replace: (href: string, opts: object) => void;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Base hook to integrate it with different routers
|
|
8
|
+
*
|
|
9
|
+
* @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
|
|
10
|
+
* @param {Router} [router] Router object with `push` and `replace` methods
|
|
11
|
+
* @param {?SearchParams<T>} [searchParams] searchParams from Next server component
|
|
12
|
+
*
|
|
13
|
+
* * Example:
|
|
14
|
+
* ```ts
|
|
15
|
+
* export const form = { name: '' };
|
|
16
|
+
* const router = { push: () => {}, replace: () => {} };
|
|
17
|
+
* const { state, updateState, updateUrl } = useUrlStateBase(form, router, sp);
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/integrations/packages/urlstate/useUrlStateBase#api}
|
|
21
|
+
*/
|
|
22
|
+
export declare function useUrlStateBase<T extends JSONCompatible>(defaultState: T, router: Router, searchParams?: object): {
|
|
23
|
+
updateState: (value: T | DeepReadonly<T> | ((currState: T) => T)) => void;
|
|
24
|
+
updateUrl: (value?: T | DeepReadonly<T> | ((currState: T) => T) | undefined, options?: Options) => void;
|
|
25
|
+
state: DeepReadonly<T>;
|
|
26
|
+
getState: () => T;
|
|
27
|
+
};
|
|
28
|
+
interface Options extends Object {
|
|
29
|
+
replace?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"react";import{parseSsrQs as e}from"../encoder/encoder.mjs";import{useInsertionEffect as o}from"../useInsertionEffect.mjs";import{useSharedState as n}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as r}from"../useUrlEncode/useUrlEncode.mjs";import{isSSR as s}from"../utils.mjs";function a(a,i,m){const{parse:d,stringify:p}=r(a),{state:l,getState:u,setState:w}=n(a,(()=>s()?e(m,a):d(window.location.search)));o((()=>{const t=()=>{const t=d(window.location.search);w(t)};return window.addEventListener(c,t),()=>{window.removeEventListener(c,t)}}),[w]);const f=t.useCallback(((t,e)=>{const o=window.location.search,n=`${window.location.pathname}${o.length&&!o.includes("?")?"?":""}${o}`;let r,s;"function"==typeof t?(r=t(u()),s=p(r)):(r=t??u(),s=p(r)),w(r);const a=`${window.location.pathname}${s.length?"?":""}${s}`;if(n!==a){const{replace:t,...o}=e||{};i[t?"replace":"push"](a,{...o})}}),[i,p,u]);return{updateState:w,updateUrl:f,state:l,getState:u}}const c="popstate";export{a as useUrlStateBase};
|
package/dist/utils.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t=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},e=()=>"undefined"==typeof window,n=t=>new URLSearchParams("string"==typeof t?r(t):t?.toString?.()||""),r=t=>t.split("?")?.[1]||t||"",o=(t,e)=>JSON.stringify(t)===JSON.stringify(e);export{n as getParams,o as isEqual,e as isSSR,t as typeOf};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0-beta.2",
|
|
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": [
|
|
@@ -42,27 +42,35 @@
|
|
|
42
42
|
"files": [
|
|
43
43
|
"dist"
|
|
44
44
|
],
|
|
45
|
-
"main": "dist/index.
|
|
45
|
+
"main": "dist/index.mjs",
|
|
46
46
|
"exports": {
|
|
47
|
-
"
|
|
47
|
+
".": {
|
|
48
48
|
"types": "./dist/index.d.ts",
|
|
49
49
|
"default": "./dist/index.mjs"
|
|
50
50
|
},
|
|
51
|
-
"
|
|
52
|
-
"types": "./dist/index.d.ts",
|
|
53
|
-
"default": "./dist/
|
|
51
|
+
"./next": {
|
|
52
|
+
"types": "./dist/next/useUrlState/index.d.ts",
|
|
53
|
+
"default": "./dist/next/useUrlState/useUrlState.mjs"
|
|
54
54
|
},
|
|
55
|
-
"
|
|
56
|
-
"types": "./dist/index.d.ts",
|
|
57
|
-
"default": "./dist/
|
|
55
|
+
"./encodeState": {
|
|
56
|
+
"types": "./dist/next/encodeState/index.d.ts",
|
|
57
|
+
"default": "./dist/next/encodeState/encodeState.mjs"
|
|
58
|
+
},
|
|
59
|
+
"./encoder": {
|
|
60
|
+
"types": "./dist/next/encoder/index.d.ts",
|
|
61
|
+
"default": "./dist/next/encoder/encoder.mjs"
|
|
58
62
|
},
|
|
59
|
-
"
|
|
60
|
-
"types": "./dist/index.
|
|
61
|
-
"default": "./dist/
|
|
63
|
+
"./useSharedState": {
|
|
64
|
+
"types": "./dist/next/useSharedState/index.d.ts",
|
|
65
|
+
"default": "./dist/next/useSharedState/useSharedState.mjs"
|
|
62
66
|
},
|
|
63
|
-
"
|
|
64
|
-
"types": "./dist/index.
|
|
65
|
-
"default": "./dist/
|
|
67
|
+
"./useUrlEncode": {
|
|
68
|
+
"types": "./dist/next/useUrlEncode/index.d.ts",
|
|
69
|
+
"default": "./dist/next/useUrlEncode/useUrlEncode.mjs"
|
|
70
|
+
},
|
|
71
|
+
"./useUrlStateBase": {
|
|
72
|
+
"types": "./dist/next/useUrlStateBase/index.d.ts",
|
|
73
|
+
"default": "./dist/next/useUrlStateBase/useUrlStateBase.mjs"
|
|
66
74
|
}
|
|
67
75
|
},
|
|
68
76
|
"types": "./dist/index.d.ts",
|
|
@@ -90,8 +98,7 @@
|
|
|
90
98
|
"test": {
|
|
91
99
|
"dependencies": [
|
|
92
100
|
"tsc",
|
|
93
|
-
"test:unit"
|
|
94
|
-
"kill-next"
|
|
101
|
+
"test:unit"
|
|
95
102
|
],
|
|
96
103
|
"command": "npm run test:int -- --retries=1"
|
|
97
104
|
},
|
|
@@ -115,7 +122,8 @@
|
|
|
115
122
|
},
|
|
116
123
|
"dev": {
|
|
117
124
|
"dependencies": [
|
|
118
|
-
"dev:lib"
|
|
125
|
+
"dev:lib",
|
|
126
|
+
"kill-next"
|
|
119
127
|
],
|
|
120
128
|
"service": true,
|
|
121
129
|
"command": "npm run dev:nextjs"
|
package/dist/index.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react"),t=require("next/navigation");function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r=n(e);const o={string:"◖",boolean:"🗵",null:"∙null",undefined:"∙undefined",number:"∓",date:"⏲"},s=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=()=>"undefined"==typeof window,u=e=>new URLSearchParams("string"==typeof e?a(e):e?.toString?.()||""),a=e=>e.split("?")?.[1]||e||"",i=(e,t)=>JSON.stringify(e)===JSON.stringify(t);function l(e){switch(s(e)){case"function":case"symbol":return"";case"date":return o.date+e.toISOString();case"string":return`${o.string}${encodeURIComponent(e)}`;case"number":return o.number+String(e);case"boolean":return o.boolean+String(e);case"object":case"array":return JSON.stringify(e,d).replaceAll('"',"'");case"null":return o.null;case"undefined":return o.undefined;default:return String(e)}}function f(e,t){return function(e,t){try{return JSON.parse(e,y)}catch{const n=p(e);return n!==g?n:t}}(e.replaceAll("'",'"'),t)}const d=(e,t)=>{const n=s(t);return e&&"object"!==n&&"array"!==n?l(t):t},p=e=>e===o.null?null:e!==o.undefined?e?.startsWith?.(o.number)?Number.parseFloat(e.replace(o.number,"")):e?.startsWith?.(o.boolean)?!!e.includes("true"):e?.startsWith?.(o.date)?new Date(e.slice(1)):e?.startsWith?.(o.string)?decodeURIComponent(e).replace(/^◖/,""):g:void 0,g=Symbol("isError"),y=(e,t)=>{const n="string"==typeof t,r=n&&p(t);return r===g?t:e&&n?r:t};const b=(e,t)=>{const n="string"==typeof t,r=n&&f(t?.replaceAll?.("'",'"'));return e&&n?r:t};function S(e,t){try{return JSON.parse(e,b)}catch{const n=p(e);return n!==g?n:t}}function h(e,t,n){const r=u(n);return Object.entries(e||{}).forEach((([e,n])=>{const o=t?.[e];JSON.stringify(n)!==JSON.stringify(o)&&r.set(e,l(n))})),r.toString()}function w(e,t){return{...t||{},...Object.fromEntries([...u(e).entries()].map((([e,n])=>{const r=t?.[e];return[e,f(n,r)??r]})))}}const m=new WeakMap,O=new WeakMap,v={get:e=>O.get(e)||[],add(e,t){const n=this.get(e);return O.set(e,n.concat(t)),()=>this.remove(e,t)},remove(e,t){const n=this.get(e).filter((e=>e!==t));n.length?O.set(e,n):O.delete(e)}},E={get:function(e){return m.get(e)},set:function(e,t){m.set(e,t)}},j=r.default.useInsertionEffect||r.default.useEffect;function N(e){const t=r.default.useCallback((function(t,n){return"object"===s(t)?h(t,e,n):""}),[e]);return{parse:r.default.useCallback((function(t){return w(t,e)}),[e]),stringify:t}}exports.decode=f,exports.decodeState=w,exports.encode=l,exports.encodeState=h,exports.useUrlEncode=N,exports.useUrlState=function(e,n){const{parse:o,stringify:s}=N(e),{state:u,getState:a,setState:l}=function(e,t){const n=r.default.useRef(e),[o,s]=r.default.useState((()=>{if(c())return t?t?.():n.current;const e=t?.()||E.get(n.current)||n.current,r=E.get(n.current);return i(r,e)||E.set(n.current,e),e})),u=r.default.useCallback((e=>{const t=E.get(n.current);if("function"==typeof e){const r=e(t);if(i(t,r))return;E.set(n.current,r),v.get(n.current).forEach((e=>{e()}))}else{if(i(t,e))return;E.set(n.current,e),v.get(n.current).forEach((e=>{e()}))}}),[]);return j((()=>{const e=v.add(n.current,(()=>{s(E.get(n.current)||n.current)}));return()=>{e()}}),[]),{state:o,getState:r.default.useCallback((()=>E.get(n.current)||n.current),[]),setState:u}}(e,(()=>{return c()?(t=n,r=e,{...r,...S(JSON.stringify(t),r)}):o(window.location.search);var t,r})),f=t.useRouter();r.default.useInsertionEffect((()=>{const e=()=>{const e=o(window.location.search);l(e)},t="popstate";return window.addEventListener(t,e),()=>{window.removeEventListener(t,e)}}),[l]);const d=r.default.useCallback(((e,t)=>{const n=window.location.search,r=`${window.location.pathname}${n.length&&!n.includes("?")?"?":""}${n}`;let o,c;"function"==typeof e?(o=e(a()),c=s(o)):(o=e??a(),c=s(o)),l(o);const u=`${window.location.pathname}${c.length?"?":""}${c}`;if(r!==u){const{replace:e,...n}=t||{};f[e?"replace":"push"](u,{scroll:!1,...n})}}),[f,s,a]);return{updateState:l,updateUrl:d,state:u}};
|
package/dist/urlstate/index.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { decode, encode, parseSsrQs } from './encoder';
|
|
2
|
-
import { decodeState, encodeState } from './encodeState';
|
|
3
|
-
import { useSharedState } from './useSharedState';
|
|
4
|
-
import { useUrlEncode } from './useUrlEncode';
|
|
5
|
-
import { useUrlState } from './useUrlState';
|
|
6
|
-
import { isSSR, type JSONCompatible, type Type, typeOf } from './utils';
|
|
7
|
-
export { decode, decodeState, encode, encodeState, isSSR, parseSsrQs, typeOf, useSharedState, useUrlEncode, useUrlState, };
|
|
8
|
-
export type { JSONCompatible, Type };
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|