state-in-url 2.0.1 → 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 CHANGED
@@ -1,3 +1,5 @@
1
+ [English](./README.md) | [한국어](./README.KO.md) | 简体中文
2
+
1
3
  <h1 align="center">state-in-url</h1>
2
4
 
3
5
  <div align="center">
package/README.KO.md CHANGED
@@ -1,16 +1,19 @@
1
- [English](./README.md) | 한국어 | [简体中文](./README.CN.md)
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 검증 기능을 갖춘 관련 없는 React 컴포넌트 간 상태 공유를 쉽게 할 수 있습니다. 번거로움이나 보일러플레이트 없이 말이죠.</div>
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
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
15
18
  ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
16
19
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/73be54068b7f41b0b74a252579ac09ec)](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
  ![데모-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
26
29
 
27
- <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
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
- - 🔗 전체 애플리케이션 상태를 포함한 공유 가능한 URL
46
- - 🔄 페이지 새로고침 시 쉬운 상태 유지
50
+ - 🙃 URL을 변경하지 않고 다른 컴포넌트 간에 상태를 공유합니다. 시그널 및 기타 상태 관리 도구의 대안으로 좋습니다.
51
+ - 🔗 전체 애플리케이션 상태가 포함된 공유 가능한 URL
52
+ - 🔄 페이지 새로고침 시 상태를 쉽게 유지
47
53
  - 🧠 관련 없는 클라이언트 컴포넌트 간 데이터 동기화
48
- - 🧮 URL에 저장되지 않은 사용자 양식 저장
54
+ - 🧮 저장되지 않은 사용자 양식을 URL에 저장
49
55
 
50
56
  # 특징
51
57
 
52
- - 🧩 **간단함**: 프로바이더, 리듀서, 보일러플레이트 또는 새로운 개념 없음
53
- - 📘 **TypeScript 지원 및 타입 안전성**: 데이터 타입과 구조 보존, IDE 제안, 강력한 타이핑 및 JSDoc 주석을 통한 개발자 경험 향상
54
- - ⚛️ **프레임워크 유연성**: Next.js 및 React.js 애플리케이션을 위한 별도의 훅, 그리고 순수 JS를 위한 함수
55
- - ⚙ **잘 테스트됨**: 단위 테스트 및 Playwright 테스트
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` 함수](#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
- [문서](packages/urlstate/useUrlState/README.md)
101
+ [문서](ackages/urlstate/next/useUrlState#api)
94
102
 
95
- `useUrlState`는 Next.js 애플리케이션을 위한 커스텀 React 훅으로, 클라이언트 컴포넌트 간 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주며, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유하는 방법을 제공합니다.
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
- // 실수로 상태를 직접 변경하지 못하게 합니다, TS 필요
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
- // React.useState와 동일한 API
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
- 변경사항을 URL에 동기화
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
- ## 순수 JS 사용을 위한 `encodeState` 및 `decodeState` 헬퍼
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`를 사용하고, 변경 사항을 URL에 동기화하려면 `updateUrl`을 사용하세요
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 서버는 헤더 크기(쿼리 문자열 및 기타 내용)를 **14KB**로 제한하므로 URL 상태를 약 5000단어 이하로 유지하세요. https://vercel.com/docs/errors/URL_TOO_LONG
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
- 이 저장소를 복제하고, `npm install`을 실행한 다음
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
- - 버그 보고, 기능 요청 또는 질문을 위해 [GitHub 이슈](https://github.com/asmyshlyaev177/state-in-url/issues)를 생성하세요
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
 
@@ -1 +1 @@
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,m){const p=t(),{state:u,updateState:i,updateUrl:n,getState:f}=s(o,p,m),l=r.useCallback(((t,e)=>{n(t,{scroll:!1,...e})}),[n]),c=e();return r.useEffect((()=>{i(a(Object.fromEntries([...c.entries()]),o))}),[c]),{updateState:i,updateUrl:l,state:u,getState:f}}export{o as useUrlState};
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};
@@ -1 +1 @@
1
- import t from"react";import{parseSsrQs as e}from"../next/parseSsrQs.mjs";import{useInsertionEffect as o}from"../useInsertionEffect.mjs";import{useSharedState as n}from"../useSharedState/useSharedState.mjs";import{useUrlEncode as a}from"../useUrlEncode/useUrlEncode.mjs";import{isSSR as s}from"../utils.mjs";function r(r,i,m){const{parse:w,stringify:l}=a(r),{state:d,getState:p,setState:h}=n(r,(()=>s()?e(m,r):w(window.location.search)));o((()=>{const t=()=>{const t=w(window.location.search);h(t)};return window.addEventListener(c,t),()=>{window.removeEventListener(c,t)}}),[h]),t.useCallback((()=>{const t=Object.keys(r),e=window.location.search,o=new URLSearchParams(e),n=new URLSearchParams;return o.forEach(((e,o)=>!t.includes(o)&&n.set(o,e))),n}),[]);const u=t.useCallback(((t,e)=>{const o=`${window.location.pathname}${window.location.search}${window.location.hash}`;let n,a;"function"==typeof t?(n=t(p()),a=l(n)):(n=t??p(),a=l(n)),h(n);const s=`${window.location.pathname}${a.length?"?":""}${a}${window.location.hash}`;if(o!==s){const{replace:t,...o}=e||{};i[t?"replace":"push"](s,{...o})}}),[i,l,p]);return{updateState:h,updateUrl:u,state:d,getState:p}}const c="popstate";export{r as useUrlStateBase};
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,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "2.0.1",
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": [
@@ -84,6 +84,7 @@
84
84
  "scripts": {
85
85
  "test:unit": "wireit",
86
86
  "test:int": "wireit",
87
+ "test:int:ci": "wireit",
87
88
  "test": "wireit",
88
89
  "tsc": "wireit",
89
90
  "dev": "wireit",
@@ -106,17 +107,23 @@
106
107
  "tsc",
107
108
  "test:unit"
108
109
  ],
109
- "command": "npm run test:int -- --retries=2 --project=chromium"
110
+ "command": "npm run test:int"
110
111
  },
111
112
  "kill-next": {
112
113
  "command": "ps aux | grep .next | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1; exit 0"
113
114
  },
114
- "test:int": {
115
+ "test:int:ci": {
115
116
  "command": "playwright test",
116
117
  "dependencies": [
117
118
  "example:react"
118
119
  ]
119
120
  },
121
+ "test:int": {
122
+ "command": "playwright test -j 3 --project=chromium --retries=2",
123
+ "dependencies": [
124
+ "example:react"
125
+ ]
126
+ },
120
127
  "tsc": {
121
128
  "command": "npx tsc --noEmit"
122
129
  },