state-in-url 2.0.0 → 2.0.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 +2 -0
- package/README.KO.md +133 -41
- package/dist/constants/index.mjs +1 -0
- package/dist/encodeState/index.d.ts +1 -1
- package/dist/encodeState/index.mjs +1 -0
- package/dist/encoder/encoder.d.ts +0 -8
- package/dist/encoder/encoder.mjs +1 -1
- package/dist/encoder/index.d.ts +1 -1
- package/dist/encoder/index.mjs +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.mjs +1 -0
- package/dist/next/parseSsrQs.d.ts +9 -0
- package/dist/next/parseSsrQs.mjs +1 -0
- package/dist/next/useUrlState/index.mjs +1 -0
- package/dist/next/useUrlState/useUrlState.mjs +1 -1
- package/dist/useSharedState/index.mjs +1 -0
- package/dist/useUrlEncode/index.d.ts +1 -1
- package/dist/useUrlEncode/index.mjs +1 -0
- package/dist/useUrlStateBase/index.d.ts +1 -1
- package/dist/useUrlStateBase/index.mjs +1 -0
- package/dist/useUrlStateBase/useUrlStateBase.mjs +1 -1
- package/package.json +30 -17
package/README.CN.md
CHANGED
package/README.KO.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
[English]
|
|
1
|
+
[English] | 한국어 | [简体中文](./README.CN.md)
|
|
2
2
|
|
|
3
3
|
<h1 align="center">state-in-url</h1>
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
<div align="center">
|
|
6
7
|
<img src="/assets/logo.svg?raw=true" alt="라이브러리 로고"/>
|
|
7
8
|
|
|
8
|
-
<div>IDE 자동 완성 및 TS
|
|
9
|
+
<div>IDE 자동 완성 및 TS 검증과 함께 관련 없는 React 컴포넌트 간에 상태를 쉽게 공유할 수 있습니다. 번거로움이나 보일러플레이트 없이 사용 가능합니다.</div>
|
|
9
10
|
</div>
|
|
10
11
|
|
|
12
|
+
|
|
11
13
|
<div align="center">
|
|
12
14
|
<hr />
|
|
13
15
|
|
|
16
|
+
|
|
14
17
|
[](https://www.npmjs.com/package/state-in-url)
|
|
15
18
|

|
|
16
19
|
[](https://app.codacy.com/gh/asmyshlyaev177/state-in-url/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
@@ -24,46 +27,51 @@
|
|
|
24
27
|
|
|
25
28
|

|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
|
|
31
|
+
<a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">데모</a> |
|
|
28
32
|
<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
33
|
|
|
34
|
+
|
|
30
35
|
<hr />
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
프로젝트를 지원하려면 ⭐️을 추가해주세요!
|
|
33
38
|
|
|
34
|
-
<hr />
|
|
39
|
+
<hr />
|
|
35
40
|
|
|
36
|
-
</div>
|
|
41
|
+
</div>
|
|
37
42
|
|
|
38
|
-
## state-in-url을
|
|
43
|
+
## state-in-url을 사용해야 하는 이유는?
|
|
44
|
+
|
|
45
|
+
`state-in-url`은 URL 동기화와 함께 간단한 상태 관리를 제공합니다. 관련 없는 React 컴포넌트 간에 복잡한 상태를 공유하고, 상태를 URL과 동기화하며, TS 친화적이고 NextJS와 호환됩니다.
|
|
39
46
|
|
|
40
|
-
`state-in-url`은 URL 동기화 기능을 갖춘 간단한 상태 관리 도구입니다.
|
|
41
47
|
|
|
42
48
|
# 사용 사례
|
|
43
49
|
|
|
44
|
-
- 🙃 URL을 변경하지 않고 다른 컴포넌트
|
|
45
|
-
- 🔗 전체 애플리케이션
|
|
46
|
-
- 🔄 페이지 새로고침 시
|
|
50
|
+
- 🙃 URL을 변경하지 않고 다른 컴포넌트 간에 상태를 공유합니다. 시그널 및 기타 상태 관리 도구의 대안으로 좋습니다.
|
|
51
|
+
- 🔗 전체 애플리케이션 상태가 포함된 공유 가능한 URL
|
|
52
|
+
- 🔄 페이지 새로고침 시 상태를 쉽게 유지
|
|
47
53
|
- 🧠 관련 없는 클라이언트 컴포넌트 간 데이터 동기화
|
|
48
|
-
- 🧮
|
|
54
|
+
- 🧮 저장되지 않은 사용자 양식을 URL에 저장
|
|
49
55
|
|
|
50
56
|
# 특징
|
|
51
57
|
|
|
52
|
-
- 🧩 **간단함**: 프로바이더, 리듀서, 보일러플레이트 또는 새로운
|
|
53
|
-
- 📘 **TypeScript 지원 및 타입 안전성**: 데이터 타입과
|
|
54
|
-
- ⚛️ **프레임워크 유연성**: Next.js
|
|
55
|
-
- ⚙
|
|
58
|
+
- 🧩 **간단함**: 프로바이더, 리듀서, 보일러플레이트 또는 새로운 개념이 필요 없습니다.
|
|
59
|
+
- 📘 **TypeScript 지원 및 타입 안전성**: 데이터 타입과 구조를 보존하고, IDE 제안, 강력한 타입 지정 및 JSDoc 주석으로 개발자 경험을 향상시킵니다.
|
|
60
|
+
- ⚛️ **프레임워크 유연성**: Next.js와 React.js 애플리케이션을 위한 별도의 훅과 순수 JS를 위한 함수 제공
|
|
61
|
+
- ⚙ **철저한 테스트**: 단위 테스트 및 Playwright 테스트
|
|
56
62
|
- ⚡ **빠름**: 최소한의 리렌더링
|
|
57
|
-
- 🪶 **경량**:
|
|
63
|
+
- 🪶 **경량**: 작은 크기를 위해 의존성이 없습니다.
|
|
64
|
+
|
|
58
65
|
|
|
59
66
|
## 목차
|
|
60
67
|
|
|
61
68
|
- [설치](#설치)
|
|
62
69
|
- [Next.js용 `useUrlState`](#nextjs용-useurlstate-훅)
|
|
70
|
+
- [React.js용 `useSharedState`](#reactjs용-usesharedstate-훅)
|
|
63
71
|
- [React.js용 `useUrlEncode`](#reactjs용-useurlencode-훅)
|
|
64
72
|
- [순수 JS 사용을 위한 `encodeState` 및 `decodeState`](#순수-js-사용을-위한-encodestate-및-decodestate-헬퍼)
|
|
65
|
-
- [URL과 상태 자동 동기화](
|
|
66
|
-
- [저수준 `encode` 및 `decode` 함수](
|
|
73
|
+
- [URL과 상태 자동 동기화](#url과-상태-자동-동기화)
|
|
74
|
+
- [저수준 `encode` 및 `decode` 함수](#저수준-encode-및-decode-함수)
|
|
67
75
|
- [모범 사례](#모범-사례)
|
|
68
76
|
- [주의사항](#주의사항)
|
|
69
77
|
- [연락 및 지원](#연락-및-지원)
|
|
@@ -84,15 +92,15 @@ yarn add state-in-url
|
|
|
84
92
|
pnpm add state-in-url
|
|
85
93
|
```
|
|
86
94
|
|
|
87
|
-
### 2. tsconfig.json
|
|
95
|
+
### 2. tsconfig.json 수정
|
|
88
96
|
|
|
89
|
-
`tsconfig.json`에서 `"moduleResolution": "Node16"` 또는 `"moduleResolution": "NodeNext"`로
|
|
97
|
+
`tsconfig.json`의 `compilerOptions`에서 `"moduleResolution": "Node16"` 또는 `"moduleResolution": "NodeNext"`로 설정하세요.
|
|
90
98
|
|
|
91
99
|
## Next.js용 useUrlState 훅
|
|
92
100
|
|
|
93
|
-
[문서](
|
|
101
|
+
[문서](ackages/urlstate/next/useUrlState#api)
|
|
94
102
|
|
|
95
|
-
`useUrlState`는 Next.js 애플리케이션을 위한
|
|
103
|
+
`useUrlState`는 Next.js 애플리케이션을 위한 사용자 정의 React 훅으로, 클라이언트 컴포넌트 간의 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주어, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유할 수 있습니다.
|
|
96
104
|
|
|
97
105
|
### 사용 예시
|
|
98
106
|
|
|
@@ -102,7 +110,7 @@ pnpm add state-in-url
|
|
|
102
110
|
|
|
103
111
|
```typescript
|
|
104
112
|
// countState.ts
|
|
105
|
-
// 상태 형태는 상수에
|
|
113
|
+
// 상태 형태는 상수에 저장해야 하며, 객체를 직접 전달하지 마세요
|
|
106
114
|
export const countState: CountState = { count: 0 }
|
|
107
115
|
|
|
108
116
|
type CountState = { count: number }
|
|
@@ -112,17 +120,17 @@ pnpm add state-in-url
|
|
|
112
120
|
|
|
113
121
|
```typescript
|
|
114
122
|
'use client'
|
|
115
|
-
import { useUrlState } from 'state-in-url';
|
|
123
|
+
import { useUrlState } from 'state-in-url/next';
|
|
116
124
|
|
|
117
125
|
import { countState } from './countState';
|
|
118
126
|
|
|
119
127
|
function MyComponent() {
|
|
120
|
-
// 서버 컴포넌트에서 searchParams 사용
|
|
128
|
+
// 서버 컴포넌트에서 searchParams 사용 시
|
|
121
129
|
// 예: export default async function Home({ searchParams }: { searchParams: object }) {
|
|
122
130
|
// const { state, updateState, updateUrl } = useUrlState(countState, searchParams);
|
|
123
131
|
const { state, updateState, updateUrl } = useUrlState(countState);
|
|
124
132
|
|
|
125
|
-
// 실수로 상태를 직접 변경하지 못하게
|
|
133
|
+
// 실수로 상태를 직접 변경하지 못하게 합니다. TS 필요
|
|
126
134
|
// state.count = 2 // <- 오류
|
|
127
135
|
|
|
128
136
|
return (
|
|
@@ -133,24 +141,24 @@ function MyComponent() {
|
|
|
133
141
|
증가 (URL 업데이트)
|
|
134
142
|
</button>
|
|
135
143
|
|
|
136
|
-
|
|
144
|
+
// React.useState와 동일한 API
|
|
137
145
|
<button onClick={() => updateState(currState => ({...currState, count: currState.count + 1 }) )}>
|
|
138
146
|
증가 (로컬만)
|
|
139
147
|
</button>
|
|
140
148
|
<button onClick={() => updateUrl()}>
|
|
141
|
-
|
|
149
|
+
변경 사항을 URL에 동기화
|
|
142
150
|
// 또는 동기화하지 않고 상태만 공유
|
|
143
151
|
</button>
|
|
144
152
|
|
|
145
153
|
<button onClick={() => updateUrl(state)}>
|
|
146
|
-
|
|
154
|
+
초기화
|
|
147
155
|
</button>
|
|
148
156
|
</div>
|
|
149
157
|
)
|
|
150
158
|
}
|
|
151
159
|
```
|
|
152
160
|
|
|
153
|
-
#### 복잡한 상태 형태
|
|
161
|
+
#### 복잡한 상태 형태 사용
|
|
154
162
|
|
|
155
163
|
```typescript
|
|
156
164
|
interface UserSettings {
|
|
@@ -168,7 +176,7 @@ export const userSettings: UserSettings {
|
|
|
168
176
|
|
|
169
177
|
```typescript
|
|
170
178
|
'use client'
|
|
171
|
-
import { useUrlState } from 'state-in-url';
|
|
179
|
+
import { useUrlState } from 'state-in-url/next';
|
|
172
180
|
|
|
173
181
|
import { userSettings } from './userSettings';
|
|
174
182
|
|
|
@@ -203,7 +211,7 @@ function SettingsComponent() {
|
|
|
203
211
|
<p>테마: {state.theme}</p>
|
|
204
212
|
<p>글꼴 크기: {state.fontSize}px</p>
|
|
205
213
|
<button onClick={toggleTheme}>테마 전환</button>
|
|
206
|
-
{/* 다른 설정을 업데이트하기 위한 UI
|
|
214
|
+
{/* 다른 설정을 업데이트하기 위한 UI 요소 */}
|
|
207
215
|
</div>
|
|
208
216
|
);
|
|
209
217
|
}
|
|
@@ -238,11 +246,76 @@ function Component() {
|
|
|
238
246
|
}, [state, updateUrl]);
|
|
239
247
|
```
|
|
240
248
|
|
|
241
|
-
####
|
|
249
|
+
#### 서버 사이드 렌더링 사용
|
|
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
|
+
#### `layout` 컴포넌트에서 훅 사용
|
|
270
|
+
|
|
271
|
+
이는 까다로운 부분입니다. Next.js의 app 라우터는 서버 사이드에서 searchParams에 접근할 수 없게 합니다. 미들웨어를 사용하는 해결책이 있지만, 이는 아름답지 않고 Next.js 업데이트 후 작동이 중단될 수 있습니다.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// 적절한 `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
|
+
// 대상 레이아웃 컴포넌트
|
|
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
|
+
#### 임의의 상태 형태 사용 (권장하지 않음)
|
|
242
315
|
|
|
243
316
|
```typescript
|
|
244
317
|
'use client'
|
|
245
|
-
import { useUrlState } from 'state-in-url';
|
|
318
|
+
import { useUrlState } from 'state-in-url/next';
|
|
246
319
|
|
|
247
320
|
const someObj = {};
|
|
248
321
|
|
|
@@ -251,11 +324,27 @@ function SettingsComponent() {
|
|
|
251
324
|
}
|
|
252
325
|
```
|
|
253
326
|
|
|
327
|
+
## React.js용 `useSharedState` 훅
|
|
328
|
+
React 컴포넌트 간에 상태를 공유하기 위한 훅으로, Next.js와 Vite에서 테스트되었습니다.
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
'use client'
|
|
332
|
+
import { useSharedState } from 'state-in-url';
|
|
333
|
+
|
|
334
|
+
export const someState = { name: '' };
|
|
335
|
+
|
|
336
|
+
function SettingsComponent() {
|
|
337
|
+
const { state, setState } = useSharedState(someState);
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
[문서](packages/urlstate/useSharedState/README.md)
|
|
342
|
+
|
|
254
343
|
## React.js용 `useUrlEncode` 훅
|
|
255
344
|
|
|
256
345
|
[문서](packages/urlstate/useUrlEncode/README.md)
|
|
257
346
|
|
|
258
|
-
##
|
|
347
|
+
## `encodeState` 및 `decodeState` 헬퍼
|
|
259
348
|
|
|
260
349
|
[문서](packages/urlstate/encodeState/README.md)
|
|
261
350
|
|
|
@@ -265,20 +354,22 @@ function SettingsComponent() {
|
|
|
265
354
|
|
|
266
355
|
## 모범 사례
|
|
267
356
|
|
|
268
|
-
- 일관성을
|
|
357
|
+
- 일관성을 위해 상태 형태를 상수로 정의하세요
|
|
269
358
|
- 향상된 타입 안전성과 자동 완성을 위해 TypeScript를 사용하세요
|
|
270
359
|
- URL 매개변수에 민감한 정보를 저장하지 마세요
|
|
271
|
-
- 빈번한 업데이트에는 `updateState
|
|
360
|
+
- 빈번한 업데이트에는 `updateState`를, URL에 변경 사항을 동기화하려면 `updateUrl`을 사용하세요
|
|
361
|
+
- Next.js에서 클라이언트 컴포넌트를 감싸려면 `Suspense`를 사용하세요
|
|
272
362
|
- 읽기 쉬운 TS 오류를 위해 이 [확장 프로그램](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)을 사용하세요
|
|
273
363
|
|
|
274
364
|
## 주의사항
|
|
275
365
|
|
|
276
366
|
1. 직렬화 가능한 값만 전달할 수 있습니다. `Function`, `BigInt` 또는 `Symbol`은 작동하지 않으며, `ArrayBuffer`와 같은 것들도 마찬가지일 것입니다.
|
|
277
|
-
2. Vercel 서버는 헤더
|
|
367
|
+
2. Vercel 서버는 헤더(쿼리 문자열 및 기타 항목)의 크기를 **14KB**로 제한하므로 URL 상태를 ~5000 단어 이하로 유지하세요. https://vercel.com/docs/errors/URL_TOO_LONG
|
|
368
|
+
3. app 라우터가 있는 `next.js` 14에서 테스트되었으며, pages를 지원할 계획은 없습니다.
|
|
278
369
|
|
|
279
370
|
## 로컬에서 실행
|
|
280
371
|
|
|
281
|
-
이 저장소를
|
|
372
|
+
이 저장소를 복제하고 `npm install`을 실행한 다음
|
|
282
373
|
|
|
283
374
|
```sh
|
|
284
375
|
npm run dev
|
|
@@ -286,9 +377,10 @@ npm run dev
|
|
|
286
377
|
|
|
287
378
|
[localhost:3000](http://localhost:3000)으로 이동하세요
|
|
288
379
|
|
|
380
|
+
|
|
289
381
|
## 연락 및 지원
|
|
290
382
|
|
|
291
|
-
- 버그
|
|
383
|
+
- 버그 리포트, 기능 요청 또는 질문을 위해 [GitHub 이슈](https://github.com/asmyshlyaev177/state-in-url/issues)를 생성하세요
|
|
292
384
|
|
|
293
385
|
## [변경 로그](https://github.com/asmyshlyaev177/state-in-url/blob/main/CHANGELOG.md)
|
|
294
386
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{SYMBOLS}from"./constants.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { decodeState, encodeState } from './encodeState';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{decodeState,encodeState}from"./encodeState.mjs";
|
|
@@ -33,11 +33,3 @@ 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
|
-
*/
|
|
43
|
-
export declare function parseSsrQs<T extends JSONCompatible>(sp: object | undefined, defaults: T): T;
|
package/dist/encoder/encoder.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{SYMBOLS as
|
|
1
|
+
import{SYMBOLS as e}from"../constants/constants.mjs";import{typeOf as t}from"../utils.mjs";function r(r){switch(t(r)){case"function":case"symbol":return"";case"date":return e.date+r.toISOString();case"string":return`${e.string}${encodeURIComponent(r)}`;case"number":return e.number+String(r);case"boolean":return e.boolean+String(r);case"object":case"array":return JSON.stringify(r,s).replaceAll('"',"'");case"null":return e.null;case"undefined":return e.undefined;default:return String(r)}}function n(e,t){return c(e.replaceAll("'",'"'),t)}const s=(e,n)=>{const s=t(n);return e&&"object"!==s&&"array"!==s?r(n):n},a=t=>t===e.null?null:t!==e.undefined?t?.startsWith?.(e.number)?Number.parseFloat(t.replace(e.number,"")):t?.startsWith?.(e.boolean)?!!t.includes("true"):t?.startsWith?.(e.date)?new Date(t.slice(1)):t?.startsWith?.(e.string)?decodeURIComponent(t).replace(/^◖/,""):o:void 0,o=Symbol("isError"),u=(e,t)=>{const r="string"==typeof t,n=r&&a(t);return n===o?t:e&&r?n:t};function c(e,t){try{return JSON.parse(e,u)}catch{const r=a(e);return r!==o?r:t}}export{n as decode,a as decodePrimitive,r as encode,o as errorSym,c as parseJSON,s as replacer,u as reviver};
|
package/dist/encoder/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { decode, encode } from './encoder';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{decode,encode}from"./encoder.mjs";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { decode, encode
|
|
1
|
+
import { decode, encode } from './encoder';
|
|
2
2
|
import { decodeState, encodeState } from './encodeState';
|
|
3
|
-
import { useUrlState } from './next
|
|
3
|
+
import { parseSsrQs, useUrlState } from './next';
|
|
4
4
|
import { useSharedState } from './useSharedState';
|
|
5
5
|
import { useUrlEncode } from './useUrlEncode';
|
|
6
6
|
import { useUrlStateBase } from './useUrlStateBase';
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{decode,encode
|
|
1
|
+
export{decode,encode}from"./encoder/encoder.mjs";export{decodeState,encodeState}from"./encodeState/encodeState.mjs";export{parseSsrQs}from"./next/parseSsrQs.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";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{parseSsrQs}from"./parseSsrQs.mjs";export{useUrlState}from"./useUrlState/useUrlState.mjs";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type JSONCompatible } from '../utils';
|
|
2
|
+
/**
|
|
3
|
+
* Parses the Next.js server-side `searchParams` into an object.
|
|
4
|
+
*
|
|
5
|
+
* @param {object | undefined} sp - The server-side searchParams object to parse.
|
|
6
|
+
* @param {T} defaults - The default values to use if parsing fails.
|
|
7
|
+
* @return {T} - The parsed object or the default values.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseSsrQs<T extends JSONCompatible>(sp: object | undefined, defaults: T): T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{decodePrimitive as r,errorSym as t,decode as n}from"../encoder/encoder.mjs";function e(r,t){return{...t,...o(JSON.stringify(r),t)}}function o(n,e){try{return JSON.parse(n,c)}catch{const o=r(n);return o!==t?o:e}}const c=(r,t)=>{const e="string"==typeof t,o=e&&n(t?.replaceAll?.("'",'"'));return r&&e?o:t};export{e as parseSsrQs};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{useUrlState}from"./useUrlState.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useRouter as t,useSearchParams as e}from"next/navigation";import r from"react";import{parseSsrQs as a}from"
|
|
1
|
+
import{useRouter as t,useSearchParams as e}from"next/navigation";import r from"react";import{parseSsrQs as a}from"../parseSsrQs.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";function o(o,i){const m=t(),{state:n,updateState:u,updateUrl:c,getState:l}=s(o,m,i),p=r.useCallback(((t,e)=>{c(t,{scroll:!1,...e})}),[c]),f=e();return r.useEffect((()=>{const t=Object.keys(o),e=Object.fromEntries([...f.entries()].filter((([e])=>t.includes(e))));u(a(e,o))}),[f]),{updateState:u,updateUrl:p,state:n,getState:l}}export{o as useUrlState};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{useSharedState}from"./useSharedState.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { useUrlEncode } from './useUrlEncode';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{useUrlEncode}from"./useUrlEncode.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { useUrlStateBase } from './useUrlStateBase';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{useUrlStateBase}from"./useUrlStateBase.mjs";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"react";import{parseSsrQs as e}from"../
|
|
1
|
+
import t from"react";import{parseSsrQs as e}from"../next/parseSsrQs.mjs";import{useInsertionEffect as n}from"../useInsertionEffect.mjs";import{useSharedState as o}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:w,stringify:l}=r(a),{state:u,getState:d,setState:f}=o(a,(()=>s()?e(function(t,e){const n=Object.keys(t),o=Object.fromEntries(Object.entries(e||{}).filter((([t])=>n.includes(t))));return o}(a,m),a):w(function(t){const e=new URLSearchParams(window.location.search),n=Object.keys(t),o=new URLSearchParams;return[...e.entries()].filter((([t])=>n.includes(t))).forEach((([t,e])=>o.set(t,e))),o.toString()}(a))));n((()=>{const t=()=>{const t=w(window.location.search);f(t)};return window.addEventListener(c,t),()=>{window.removeEventListener(c,t)}}),[f]);const h=t.useCallback(((t,e)=>{const n=`${window.location.pathname}${window.location.search}${window.location.hash}`,o="function"==typeof t,r=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}(a);let s,c;o?(s=t(d()),c=l(s,r)):(s=t??d(),c=l(s,r)),f(s);const m=`${window.location.pathname}${c.length?"?":""}${c}${window.location.hash}`;if(n!==m){const{replace:t,...n}=e||{};i[t?"replace":"push"](m,{...n})}}),[i,l,d]);return{updateState:f,updateUrl:h,state:u,getState:d}}const c="popstate";export{a as useUrlStateBase};
|
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "state-in-url",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.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": [
|
|
7
7
|
"state-management",
|
|
8
|
+
"state management",
|
|
8
9
|
"Next.js",
|
|
9
10
|
"client components communication",
|
|
11
|
+
"signals alternative",
|
|
10
12
|
"query string",
|
|
13
|
+
"search params",
|
|
11
14
|
"typescript",
|
|
12
15
|
"react.js",
|
|
13
16
|
"workflow",
|
|
@@ -49,35 +52,39 @@
|
|
|
49
52
|
"default": "./dist/index.mjs"
|
|
50
53
|
},
|
|
51
54
|
"./next": {
|
|
52
|
-
"types": "./dist/next/
|
|
53
|
-
"default": "./dist/next/
|
|
55
|
+
"types": "./dist/next/index.d.ts",
|
|
56
|
+
"default": "./dist/next/index.mjs"
|
|
54
57
|
},
|
|
55
58
|
"./encodeState": {
|
|
56
|
-
"types": "./dist/
|
|
57
|
-
"default": "./dist/
|
|
59
|
+
"types": "./dist/encodeState/index.d.ts",
|
|
60
|
+
"default": "./dist/encodeState/index.mjs"
|
|
58
61
|
},
|
|
59
62
|
"./encoder": {
|
|
60
|
-
"types": "./dist/
|
|
61
|
-
"default": "./dist/
|
|
63
|
+
"types": "./dist/encoder/index.d.ts",
|
|
64
|
+
"default": "./dist/encoder/index.mjs"
|
|
62
65
|
},
|
|
63
66
|
"./useSharedState": {
|
|
64
|
-
"types": "./dist/
|
|
65
|
-
"default": "./dist/
|
|
67
|
+
"types": "./dist/useSharedState/index.d.ts",
|
|
68
|
+
"default": "./dist/useSharedState/index.mjs"
|
|
66
69
|
},
|
|
67
70
|
"./useUrlEncode": {
|
|
68
|
-
"types": "./dist/
|
|
69
|
-
"default": "./dist/
|
|
71
|
+
"types": "./dist/useUrlEncode/index.d.ts",
|
|
72
|
+
"default": "./dist/useUrlEncode/index.mjs"
|
|
70
73
|
},
|
|
71
74
|
"./useUrlStateBase": {
|
|
72
|
-
"types": "./dist/
|
|
73
|
-
"default": "./dist/
|
|
75
|
+
"types": "./dist/useUrlStateBase/index.d.ts",
|
|
76
|
+
"default": "./dist/useUrlStateBase/index.mjs"
|
|
77
|
+
},
|
|
78
|
+
"./utils": {
|
|
79
|
+
"types": "./dist/utils.d.ts",
|
|
80
|
+
"default": "./dist/utils.mjs"
|
|
74
81
|
}
|
|
75
82
|
},
|
|
76
83
|
"types": "./dist/index.d.ts",
|
|
77
84
|
"scripts": {
|
|
78
85
|
"test:unit": "wireit",
|
|
79
86
|
"test:int": "wireit",
|
|
80
|
-
"test:int:
|
|
87
|
+
"test:int:ci": "wireit",
|
|
81
88
|
"test": "wireit",
|
|
82
89
|
"tsc": "wireit",
|
|
83
90
|
"dev": "wireit",
|
|
@@ -100,17 +107,23 @@
|
|
|
100
107
|
"tsc",
|
|
101
108
|
"test:unit"
|
|
102
109
|
],
|
|
103
|
-
"command": "npm run test:int
|
|
110
|
+
"command": "npm run test:int"
|
|
104
111
|
},
|
|
105
112
|
"kill-next": {
|
|
106
113
|
"command": "ps aux | grep .next | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1; exit 0"
|
|
107
114
|
},
|
|
108
|
-
"test:int": {
|
|
115
|
+
"test:int:ci": {
|
|
109
116
|
"command": "playwright test",
|
|
110
117
|
"dependencies": [
|
|
111
118
|
"example:react"
|
|
112
119
|
]
|
|
113
120
|
},
|
|
121
|
+
"test:int": {
|
|
122
|
+
"command": "playwright test -j 3 --project=chromium --retries=2",
|
|
123
|
+
"dependencies": [
|
|
124
|
+
"example:react"
|
|
125
|
+
]
|
|
126
|
+
},
|
|
114
127
|
"tsc": {
|
|
115
128
|
"command": "npx tsc --noEmit"
|
|
116
129
|
},
|
|
@@ -215,11 +228,11 @@
|
|
|
215
228
|
"@testing-library/jest-dom": "^6.4.2",
|
|
216
229
|
"@testing-library/react": "^16.0.0",
|
|
217
230
|
"@testing-library/react-hooks": "^8.0.1",
|
|
231
|
+
"@types/glob": "^8.1.0",
|
|
218
232
|
"@types/jest": "^29.5.12",
|
|
219
233
|
"@types/node": "^20.11.24",
|
|
220
234
|
"@types/react": "^18.3.3",
|
|
221
235
|
"@types/react-dom": "^18.3.0",
|
|
222
|
-
"chokidar-cli": "^3.0.0",
|
|
223
236
|
"commitizen": "^4.3.0",
|
|
224
237
|
"cross-env": "^7.0.3",
|
|
225
238
|
"cz": "^1.8.2",
|