state-in-url 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.CN.md CHANGED
@@ -25,6 +25,7 @@
25
25
  ![演示-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
26
26
 
27
27
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">演示</a> |
28
+ <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
28
29
  <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
30
 
30
31
  <hr />
@@ -91,7 +92,7 @@ pnpm add state-in-url
91
92
 
92
93
  ## Next.js的useUrlState钩子
93
94
 
94
- [文档](ackages/urlstate/next/useUrlState#api)
95
+ [文档](packages/urlstate/next/useUrlState#api)
95
96
 
96
97
  `useUrlState`是一个为Next.js应用程序设计的自定义React钩子,使客户端组件之间的通信变得简单。它允许你共享任何复杂的状态并将其与URL搜索参数同步,提供了一种在页面重新加载之间持久化状态并通过URL共享应用程序状态的方法。
97
98
 
package/README.KO.md CHANGED
@@ -29,6 +29,7 @@
29
29
 
30
30
 
31
31
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">데모</a> |
32
+ <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
32
33
  <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>
33
34
 
34
35
 
@@ -98,7 +99,7 @@ pnpm add state-in-url
98
99
 
99
100
  ## Next.js용 useUrlState 훅
100
101
 
101
- [문서](ackages/urlstate/next/useUrlState#api)
102
+ [문서](packages/urlstate/next/useUrlState#api)
102
103
 
103
104
  `useUrlState`는 Next.js 애플리케이션을 위한 사용자 정의 React 훅으로, 클라이언트 컴포넌트 간의 통신을 쉽게 만듭니다. 복잡한 상태를 공유하고 URL 검색 매개변수와 동기화할 수 있게 해주어, 페이지 새로고침 시 상태를 유지하고 URL을 통해 애플리케이션 상태를 공유할 수 있습니다.
104
105
 
package/README.md CHANGED
@@ -2,18 +2,15 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
2
2
 
3
3
  <h1 align="center">state-in-url</h1>
4
4
 
5
-
6
5
  <div align="center">
7
6
  <img src="/assets/logo.svg?raw=true" alt="Library logo"/>
8
7
 
9
8
  <div>Easily share state between unrelated React components, with IDE autocomplete and TS validation. Without any hasssle or boilerplate. </div>
10
9
  </div>
11
10
 
12
-
13
11
  <div align="center">
14
12
  <hr />
15
13
 
16
-
17
14
  [![npm](https://img.shields.io/npm/v/state-in-url.svg)](https://www.npmjs.com/package/state-in-url)
18
15
  ![Tests](https://github.com/asmyshlyaev177/state-in-url/actions/workflows/tests.yml/badge.svg?branch=main)
19
16
  [![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)
@@ -27,11 +24,10 @@ English | [한국어](./README.KO.md) | [简体中文](./README.CN.md)
27
24
 
28
25
  ![Demo-gif](https://github.com/asmyshlyaev177/state-in-url/assets/19854148/c9802601-4d42-4362-b3e4-37ff87c3b97f)
29
26
 
30
-
31
27
  <a href="https://state-in-url-asmyshlyaev177.vercel.app/" target="_blank">DEMO</a> |
28
+ <a href="https://state-in-url.netlify.app/" target="_blank">DEMO2</a> |
32
29
  <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>
33
30
 
34
-
35
31
  <hr />
36
32
 
37
33
  Add a ⭐️ to support the project!
@@ -40,11 +36,10 @@ Add a ⭐️ to support the project!
40
36
 
41
37
  </div>
42
38
 
43
- ## Why use state-in-url?
39
+ ## Why use state-in-url?
44
40
 
45
41
  `state-in-url` Simple state management with optional URL sync. Share complex states between unrelated React components, TS-friendly, NextJS compatible.
46
42
 
47
-
48
43
  # Use cases
49
44
 
50
45
  - 🧮 Store unsaved user forms in URL
@@ -53,8 +48,6 @@ Add a ⭐️ to support the project!
53
48
  - 🔗 Shareable URLs with full application state
54
49
  - 🔄 Easy state persistence across page reloads
55
50
 
56
-
57
-
58
51
  # Features
59
52
 
60
53
  - 🧩 **Simple**: No providers, reducers, boilerplate or new concepts
@@ -64,7 +57,6 @@ Add a ⭐️ to support the project!
64
57
  - ⚡ **Fast**: Minimal rerenders
65
58
  - 🪶 **Lightweight**: Zero dependencies for a smaller footprint
66
59
 
67
-
68
60
  ## Table of content
69
61
 
70
62
  - [Installation](#installation)
@@ -100,7 +92,7 @@ In `tsconfig.json` in `compilerOptions` set `"moduleResolution": "Node16"` or `"
100
92
 
101
93
  ## useUrlState hook for Next.js
102
94
 
103
- [Docs](ackages/urlstate/next/useUrlState#api)
95
+ [Docs](packages/urlstate/next/useUrlState#api)
104
96
 
105
97
  `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.
106
98
 
@@ -129,8 +121,10 @@ import { countState } from './countState';
129
121
  function MyComponent() {
130
122
  // for use searchParams from server component
131
123
  // e.g. export default async function Home({ searchParams }: { searchParams: object }) {
132
- // const { state, updateState, updateUrl } = useUrlState(countState, searchParams);
133
- const { state, updateState, updateUrl } = useUrlState(countState);
124
+ // const { state, updateState, updateUrl } = useUrlState({ defaultState: countState, searchParams });
125
+ // can pass `replace` arg, it's control will `updateUrl` will use `rounter.push` or `router.replace`, default replace=true
126
+ // const { state, updateState, updateUrl } = useUrlState({ defaultState: countState, searchParams, replace: false });
127
+ const { state, updateState, updateUrl } = useUrlState({ defaultState: countState });
134
128
 
135
129
  // won't let you to accidently mutate state directly, requires TS
136
130
  // state.count = 2 // <- error
@@ -184,7 +178,7 @@ import { userSettings } from './userSettings';
184
178
 
185
179
  function SettingsComponent() {
186
180
  // `state` will infer from UserSettings type!
187
- const { state, updateUrl } = useUrlState(userSettings);
181
+ const { state, updateUrl } = useUrlState({ defaultState: userSettings });
188
182
 
189
183
  const toggleTheme = () => {
190
184
  updateUrl(current => ({
@@ -221,7 +215,7 @@ function SettingsComponent() {
221
215
 
222
216
  // Other component
223
217
  function Component() {
224
- const { state } = useUrlState(defaultSettings);
218
+ const { state } = useUrlState({ defaultState: defaultSettings });
225
219
 
226
220
  return (
227
221
  <div>
@@ -248,12 +242,18 @@ function Component() {
248
242
  }, [state, updateUrl]);
249
243
  ```
250
244
 
245
+ Syncing state `onBlur` will be more aligned with real world usage.
246
+
247
+ ```typescript
248
+ <input onBlur={() => updateUrl()} .../>
249
+ ```
250
+
251
251
  #### With server side rendering
252
252
 
253
253
  ```typescript
254
254
  export default async function Home({ searchParams }: { searchParams: object }) {
255
255
  return (
256
- <Form sp={searchParams} />
256
+ <Form searchParams={searchParams} />
257
257
  )
258
258
  }
259
259
 
@@ -263,8 +263,8 @@ import React from 'react';
263
263
  import { useUrlState } from 'state-in-url/next';
264
264
  import { form } from './form';
265
265
 
266
- const Form = ({ sp }: { sp: object }) => {
267
- const { state, updateState, updateUrl } = useUrlState(form, sp);
266
+ const Form = ({ searchParams }: { searchParams: object }) => {
267
+ const { state, updateState, updateUrl } = useUrlState({ defaultState: form, searchParams });
268
268
  }
269
269
  ```
270
270
 
@@ -329,6 +329,7 @@ function SettingsComponent() {
329
329
  ```
330
330
 
331
331
  ## `useSharedState` hook for React.js
332
+
332
333
  Hook to share state between any React components, tested with Next.js and Vite.
333
334
 
334
335
  ```typescript
@@ -368,7 +369,7 @@ function SettingsComponent() {
368
369
  ## Gothas
369
370
 
370
371
  1. Can pass only serializable values, `Function`, `BigInt` or `Symbol` won't work, probably things like `ArrayBuffer` neither.
371
- 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
372
+ 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>
372
373
  3. Tested with `next.js` 14 with app router, no plans to support pages.
373
374
 
374
375
  ## Run locally
@@ -381,7 +382,6 @@ npm run dev
381
382
 
382
383
  Go to [localhost:3000](http://localhost:3000)
383
384
 
384
-
385
385
  ## Contact & Support
386
386
 
387
387
  - Create a [GitHub issue](https://github.com/asmyshlyaev177/state-in-url/issues) for bug reports, feature requests, or questions
@@ -394,7 +394,7 @@ This project is licensed under the [MIT license](https://github.com/asmyshlyaev1
394
394
 
395
395
  ## Inspiration
396
396
 
397
- [Using URL to store state in Vue ](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)
397
+ [Using URL to store state in Vue](https://dev.to/jacobandrewsky/using-url-to-store-state-in-vue-275c)
398
398
 
399
399
  [Storing state in the URL](https://antonz.org/storing-state/)
400
400
 
@@ -1,6 +1,7 @@
1
- import { useRouter } from 'next/navigation';
2
1
  import { type DeepReadonly, type JSONCompatible } from '../../utils';
3
2
  /**
3
+ * @deprecated Pass arguments in a object `useUrlState({ defaultState: form, searchParams })`
4
+ *
4
5
  * NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
5
6
  *
6
7
  * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
@@ -23,34 +24,40 @@ import { type DeepReadonly, type JSONCompatible } from '../../utils';
23
24
  * * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#api}
24
25
  */
25
26
  export declare function useUrlState<T extends JSONCompatible>(defaultState: T, searchParams?: object): {
26
- /**
27
- * * Example:
28
- * ```ts
29
- * updateState({ name: 'test' });
30
- * // or
31
- * updateState(curr => ({ ...curr, name: 'test' }) );
32
- * ```
33
- *
34
- * * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#updatestate}
35
- */
27
+ state: DeepReadonly<T>;
36
28
  updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
37
- /**
38
- * * Example:
39
- * ```ts
40
- * updateUrl({ name: 'test' });
41
- * // or
42
- * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: false } );
43
- * ```
44
- *
45
- * * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#updateurl}
46
- */
47
- updateUrl: (value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T) | undefined, options?: Options) => void;
48
- state: DeepReadonly<DeepReadonly<T>>;
49
- getState: () => T;
29
+ updateUrl: (value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
30
+ getState: () => DeepReadonly<T>;
50
31
  };
51
- type Router = ReturnType<typeof useRouter>;
52
- type RouterOptions = NonNullable<Parameters<Router['push']>[1] | Parameters<Router['replace']>[1]>;
53
- interface Options extends RouterOptions {
32
+ /**
33
+ * NextJS hook. Returns `state`, `updateState`, and `updateUrl` functions
34
+ *
35
+ * @param {JSONCompatible<T>} [defaultState] Fallback (default) values for state
36
+ * @param {?SearchParams<T>} [searchParams] searchParams from Next server component
37
+ *
38
+ * * Example:
39
+ * ```ts
40
+ * export const form = { name: '', age: 0 };
41
+ * const { state, updateState, updateUrl } = useUrlState({ defaultState: form });
42
+ * // for nextjs seerver components
43
+ * // const { state, updateState, updateUrl } = useUrlState({ defaultState: form, searchParams });
44
+ *
45
+ * updateState({ name: 'test' });
46
+ * // by default it's uses router.push with scroll: false
47
+ * updateUrl({ name: 'test' }, { replace: true, scroll: true });
48
+ * // similar to React.useState
49
+ * updateUrl(curr => ({ ...curr, name: 'test' }), { replace: true, scroll: true });
50
+ * ```
51
+ *
52
+ * * Github {@link https://github.com/asmyshlyaev177/state-in-url/tree/main/packages/urlstate/next/useUrlState#api}
53
+ */
54
+ export declare function useUrlState<T extends JSONCompatible>({ defaultState, searchParams, }: {
55
+ defaultState: T;
56
+ searchParams?: object;
54
57
  replace?: boolean;
55
- }
56
- export {};
58
+ }): {
59
+ state: DeepReadonly<T>;
60
+ updateState: (value: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
61
+ updateUrl: (value?: Partial<T> | Partial<DeepReadonly<T>> | ((currState: T) => T)) => void;
62
+ getState: () => DeepReadonly<T>;
63
+ };
@@ -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,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
+ import{useRouter as t,useSearchParams as e}from"next/navigation";import a from"react";import{parseSsrQs as r}from"../parseSsrQs.mjs";import{useUrlStateBase as s}from"../../useUrlStateBase/useUrlStateBase.mjs";function c(c,l){const{_defaultState:n,_searchParams:u,_replace:o}=function(t,e){const a="defaultState"in t?t.defaultState:t,r="defaultState"in t?t.searchParams:e,s="defaultState"in t&&(t.replace??!0);return{_defaultState:a,_searchParams:r,_replace:s}}(c,l),f=t(),{state:i,updateState:m,updateUrl:p,getState:S}=s(n,f,u),d=a.useCallback(((t,e)=>{const a={scroll:!1,replace:o,...e};p(t,a)}),[p,o]),_=e();return a.useEffect((()=>{const t=Object.keys(n),e=Object.fromEntries([..._.entries()].filter((([e])=>t.includes(e))));m(r(e,n))}),[_]),{updateState:m,updateUrl:d,state:i,getState:S}}export{c as useUrlState};
@@ -1 +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 c,isEqual as u}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[i,a]=t.useState((()=>{if(c())return o?o?.():f.current;const t=o?.()||r.get(f.current)||f.current,e=r.get(f.current);return u(e,t)||r.set(f.current,t),t})),m=t.useCallback((t=>{const n=r.get(f.current);if("function"==typeof t){const c=t(n);if(u(n,c))return;r.set(f.current,c),e.get(f.current).forEach((t=>{t()}))}else{const c={...n,...t};if(u(n,c))return;r.set(f.current,c),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};
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 c,isEqual as u}from"../utils.mjs";function s(s,o){const f=t.useRef(s),[i,a]=t.useState((()=>{if(c())return o?o?.():f.current;const t=r.get(f.current);if(t)return t;{const t=o?.()||f.current;return r.set(f.current,t),t}})),m=t.useCallback((t=>{const n=r.get(f.current);if("function"==typeof t){const c=t(n);if(u(n,c))return;r.set(f.current,c),e.get(f.current).forEach((t=>{t()}))}else{const c={...n,...t};if(u(n,c))return;r.set(f.current,c),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};
@@ -1 +1 @@
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:u,stringify:w}=r(a),{state:l,getState:f,setState:d}=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):u(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=u(window.location.search);d(t)};return window.addEventListener(c,t),()=>{window.removeEventListener(c,t)}}),[d]);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(f()),c=w(s,r),d(s)):(d((e=>{if(t){const n={...e,...t};return s=n,n}return s=e,e})),c=w(s,r));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,w,f]);return{updateState:d,updateUrl:h,state:l,getState:f}}const c="popstate";export{a 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,m,u){const{parse:w,stringify:f}=r(a),{state:l,getState:d,setState:h}=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,u),a):w(c(a))));n((()=>{const t=()=>{const t=w(c(a));h(t)};return window.addEventListener(i,t),()=>{window.removeEventListener(i,t)}}),[h]);const p=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),s=o?t(d()):t?{...d(),...t}:d(),c=f(s,r);h(s);const i=`${window.location.pathname}${c.length?"?":""}${c}${window.location.hash}`;if(n!==i){const{replace:t,...n}=e||{};m[t?"replace":"push"](i,{...n})}}),[m,f,d]);return{updateState:h,updateUrl:p,state:l,getState:d}}function c(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()}const i="popstate";export{a as useUrlStateBase};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "state-in-url",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
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": [
@@ -120,7 +120,7 @@
120
120
  ]
121
121
  },
122
122
  "test:int": {
123
- "command": "playwright test -j 3 --project=chromium --retries=2",
123
+ "command": "playwright test --project=chromium --retries=2",
124
124
  "dependencies": [
125
125
  "example:react"
126
126
  ]
@@ -229,8 +229,8 @@
229
229
  "@rollup/plugin-node-resolve": "^15.2.3",
230
230
  "@rollup/plugin-terser": "^0.4.4",
231
231
  "@rollup/plugin-typescript": "^11.1.6",
232
- "@testing-library/dom": "^10.2.0",
233
- "@testing-library/jest-dom": "^6.4.2",
232
+ "@testing-library/dom": "^10.4.0",
233
+ "@testing-library/jest-dom": "^6.4.8",
234
234
  "@testing-library/react": "^16.0.0",
235
235
  "@testing-library/react-hooks": "^8.0.1",
236
236
  "@types/glob": "^8.1.0",