state-in-url 6.1.3 → 6.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/package.json +9 -6
- package/package.json +9 -6
- package/skills/feature-state-hook/SKILL.md +352 -0
- package/skills/form-library-integration/SKILL.md +191 -0
- package/skills/input-handling/SKILL.md +160 -0
- package/skills/nextjs-ssr/SKILL.md +251 -0
- package/skills/react-router-remix-setup/SKILL.md +180 -0
- package/skills/shared-state-no-url/SKILL.md +158 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shared-state-no-url
|
|
3
|
+
description: >
|
|
4
|
+
Use useSharedState (from state-in-url, NOT a framework subpath) as a
|
|
5
|
+
framework-agnostic cross-component state primitive when URL sync is
|
|
6
|
+
explicitly NOT wanted. Same module-scoped default-state-identity rule
|
|
7
|
+
as useUrlState. A lightweight Context.Provider replacement that works
|
|
8
|
+
in React, Next.js, Vite, Remix without setup. Load this skill when the
|
|
9
|
+
user wants shared state but specifies "no URL", or for state that is
|
|
10
|
+
sensitive, ephemeral, or too large to live in the URL.
|
|
11
|
+
type: core
|
|
12
|
+
library: state-in-url
|
|
13
|
+
library_version: '6.1.3'
|
|
14
|
+
sources:
|
|
15
|
+
- 'asmyshlyaev177/state-in-url:packages/urlstate/useSharedState/'
|
|
16
|
+
- 'asmyshlyaev177/state-in-url:README.md#usesharedstate-hook-for-reactjs'
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# state-in-url — Shared state without URL sync
|
|
20
|
+
|
|
21
|
+
`useSharedState` is `state-in-url`'s lightweight cross-component state primitive — no Context provider, no Redux, no Zustand store. State is shared between any components that pass the **same module-scoped default-state object** (identity, not deep equality). It is framework-agnostic and has no URL involvement.
|
|
22
|
+
|
|
23
|
+
**Decision:** use `useUrlState` (see `state-in-url/feature-state-hook`) by default. Reach for `useSharedState` only when the user explicitly opts out of URL sync, or when the state is sensitive / ephemeral / too large for a URL.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// features/cart/cartState.ts
|
|
29
|
+
export type CartState = {
|
|
30
|
+
items: { id: string; qty: number }[];
|
|
31
|
+
isOpen: boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const CART_STATE: CartState = {
|
|
35
|
+
items: [],
|
|
36
|
+
isOpen: false,
|
|
37
|
+
};
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// any component
|
|
42
|
+
'use client';
|
|
43
|
+
import { useSharedState } from 'state-in-url'; // NB: top-level subpath
|
|
44
|
+
|
|
45
|
+
import { CART_STATE } from 'features/cart/cartState';
|
|
46
|
+
|
|
47
|
+
export function CartButton() {
|
|
48
|
+
const { state, setState } = useSharedState(CART_STATE);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<button onClick={() => setState((curr) => ({ ...curr, isOpen: !curr.isOpen }))}>
|
|
52
|
+
Cart ({state.items.length})
|
|
53
|
+
</button>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Any other component calling `useSharedState(CART_STATE)` reads and writes the same store.
|
|
59
|
+
|
|
60
|
+
## Core Patterns
|
|
61
|
+
|
|
62
|
+
### Functional update
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
setState((curr) => ({ ...curr, items: curr.items.concat(newItem) }));
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Read-only `getState` (avoid rerender)
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const { getState } = useSharedState(CART_STATE);
|
|
72
|
+
|
|
73
|
+
const onSubmit = () => {
|
|
74
|
+
const { items } = getState(); // doesn't subscribe to changes
|
|
75
|
+
api.checkout(items);
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### SSR initial state
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const { state } = useSharedState(CART_STATE, () => readFromCookie());
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The second-argument function runs once for the initial value (useful for cookie-driven SSR hydration).
|
|
86
|
+
|
|
87
|
+
## Common Mistakes
|
|
88
|
+
|
|
89
|
+
### MEDIUM Reaching for `useSharedState` when URL sync is wanted
|
|
90
|
+
|
|
91
|
+
Wrong:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Filters that the user wants to share via copy/paste link
|
|
95
|
+
const { state, setState } = useSharedState(FILTERS_STATE);
|
|
96
|
+
// Page refresh loses everything. Link sharing impossible.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Correct:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { useUrlState } from 'state-in-url/next';
|
|
103
|
+
const { urlState, setUrl } = useUrlState(FILTERS_STATE, { searchParams });
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`useSharedState` has no URL involvement. Pick `useUrlState` whenever state should survive a refresh or be shareable; pick `useSharedState` only when the user explicitly says "no URL".
|
|
107
|
+
|
|
108
|
+
Source: Maintainer interview
|
|
109
|
+
|
|
110
|
+
### HIGH Default state defined inside the React component
|
|
111
|
+
|
|
112
|
+
Wrong:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
function CartButton() {
|
|
116
|
+
const initial = { items: [], isOpen: false };
|
|
117
|
+
useSharedState(initial); // different identity in every component → no sharing
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Correct:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// cartState.ts (module scope)
|
|
125
|
+
export type CartState = { items: { id: string; qty: number }[]; isOpen: boolean };
|
|
126
|
+
export const CART_STATE: CartState = { items: [], isOpen: false };
|
|
127
|
+
|
|
128
|
+
function CartButton() {
|
|
129
|
+
useSharedState(CART_STATE);
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Same identity-based sharing rule as `useUrlState`. A non-static default breaks cross-component sharing silently — each call creates an independent store.
|
|
134
|
+
|
|
135
|
+
Source: Inferred from `useUrlState` pattern (shared internals)
|
|
136
|
+
|
|
137
|
+
### MEDIUM Storing actual secrets in shared state
|
|
138
|
+
|
|
139
|
+
Wrong:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const SESSION_STATE = { token: '', userId: '' };
|
|
143
|
+
useSharedState(SESSION_STATE);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Correct:
|
|
147
|
+
|
|
148
|
+
Use a proper auth store (cookies, secure storage, framework auth helper). `useSharedState` is in-memory and reasonable for app state, but it's not a vault — any component on the page can read it via the shared identity.
|
|
149
|
+
|
|
150
|
+
## When NOT to use this skill
|
|
151
|
+
|
|
152
|
+
- The data should be shareable by URL → `state-in-url/feature-state-hook` instead.
|
|
153
|
+
- The data is server cache (lists, paginated queries) → `@tanstack/react-query` or `swr`.
|
|
154
|
+
- The data is persisted user prefs across sessions → `localStorage` or a settings API.
|
|
155
|
+
|
|
156
|
+
## See also
|
|
157
|
+
|
|
158
|
+
- `state-in-url/feature-state-hook` — when URL sync IS wanted (the default choice).
|