@yakocloud/state-vocab 4.0.2 → 4.0.3
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 +88 -1
- package/dist/client.cjs.js +1 -1
- package/dist/client.es.js +22 -20
- package/dist/types/client.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -260,6 +260,81 @@ By default `ssr: true`:
|
|
|
260
260
|
|
|
261
261
|
This guarantees the server and client produce identical markup, and the value from storage is applied without a visible flash.
|
|
262
262
|
|
|
263
|
+
### Next.js Pages Router (SSR without RSC)
|
|
264
|
+
|
|
265
|
+
If you use Next.js with the **Pages Router** (or any SSR setup without React Server Components), you still need `ssr: true` to prevent hydration mismatches — but you don't have server components to wrap with `StateVocabProvider` from `serverify`.
|
|
266
|
+
|
|
267
|
+
In this case, wrap your app with `StateVocabClientProvider` from `@yakocloud/state-vocab/client`. It creates an isolated `VocabStore` per render, preventing state from leaking between concurrent SSR requests.
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// pages/_app.tsx
|
|
271
|
+
import type { AppProps } from 'next/app'
|
|
272
|
+
import { StateVocabClientProvider } from '@yakocloud/state-vocab/client'
|
|
273
|
+
|
|
274
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
275
|
+
return (
|
|
276
|
+
<StateVocabClientProvider>
|
|
277
|
+
<Component {...pageProps} />
|
|
278
|
+
</StateVocabClientProvider>
|
|
279
|
+
)
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Storage is still configured with `ssr: true` (the default):
|
|
284
|
+
|
|
285
|
+
```ts
|
|
286
|
+
// lib/storage.ts
|
|
287
|
+
import { setupStorage, defineState } from '@yakocloud/state-vocab'
|
|
288
|
+
import { clientify } from '@yakocloud/state-vocab/client'
|
|
289
|
+
|
|
290
|
+
const storage = setupStorage({
|
|
291
|
+
preference: {
|
|
292
|
+
theme: defineState<'Dark' | 'White' | 'System'>({
|
|
293
|
+
storage: localStorage,
|
|
294
|
+
defaultValue: 'Dark',
|
|
295
|
+
}),
|
|
296
|
+
},
|
|
297
|
+
// ssr: true is the default — storage reads are deferred until after hydration
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
export const clientStorage = clientify(storage)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Use `clientStorage` directly in any page or component — no additional wiring needed:
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
// pages/settings.tsx
|
|
307
|
+
import { clientStorage } from '@/lib/storage'
|
|
308
|
+
|
|
309
|
+
export default function Settings() {
|
|
310
|
+
const [theme, setTheme] = clientStorage.preference.theme.useState()
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
<select value={theme} onChange={(e) => setTheme(e.target.value as 'Dark' | 'White' | 'System')}>
|
|
314
|
+
<option value="Dark">Dark</option>
|
|
315
|
+
<option value="White">White</option>
|
|
316
|
+
<option value="System">System</option>
|
|
317
|
+
</select>
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
`StateVocabClientProvider` also accepts an optional `value` prop to pre-seed the store with server-fetched data (e.g., from `getServerSideProps`):
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
// pages/_app.tsx
|
|
326
|
+
import type { AppProps } from 'next/app'
|
|
327
|
+
import { StateVocabClientProvider } from '@yakocloud/state-vocab/client'
|
|
328
|
+
|
|
329
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
330
|
+
return (
|
|
331
|
+
<StateVocabClientProvider value={pageProps.initialVocab}>
|
|
332
|
+
<Component {...pageProps} />
|
|
333
|
+
</StateVocabClientProvider>
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
263
338
|
### React Server Components (RSC)
|
|
264
339
|
|
|
265
340
|
For Next.js apps using the App Router, state-vocab provides dedicated server and client entry points that let you read state in async server components and pass it down to client components via `StateVocabProvider`.
|
|
@@ -270,7 +345,7 @@ For Next.js apps using the App Router, state-vocab provides dedicated server and
|
|
|
270
345
|
|---|---|
|
|
271
346
|
| `@yakocloud/state-vocab` | Shared files — `defineState`, `setupStorage` |
|
|
272
347
|
| `@yakocloud/state-vocab/server` | Server Components — `serverify` |
|
|
273
|
-
| `@yakocloud/state-vocab/client` | Client Components — `clientify` |
|
|
348
|
+
| `@yakocloud/state-vocab/client` | Client Components — `clientify`, `StateVocabClientProvider` |
|
|
274
349
|
|
|
275
350
|
**1. Define the shared storage schema** (used on both server and client):
|
|
276
351
|
|
|
@@ -607,6 +682,18 @@ const { StateVocabProvider } = serverStorage
|
|
|
607
682
|
</StateVocabProvider>
|
|
608
683
|
```
|
|
609
684
|
|
|
685
|
+
### `StateVocabClientProvider`
|
|
686
|
+
|
|
687
|
+
A client-only provider for SSR setups **without** React Server Components (e.g. Next.js Pages Router). Import from `@yakocloud/state-vocab/client` and place it at your app root to ensure per-request store isolation. Accepts an optional `value` prop to pre-seed the store.
|
|
688
|
+
|
|
689
|
+
```tsx
|
|
690
|
+
import { StateVocabClientProvider } from '@yakocloud/state-vocab/client'
|
|
691
|
+
|
|
692
|
+
<StateVocabClientProvider value={initialVocab}>
|
|
693
|
+
<App />
|
|
694
|
+
</StateVocabClientProvider>
|
|
695
|
+
```
|
|
696
|
+
|
|
610
697
|
### `serverify<T>(storage: T)`
|
|
611
698
|
|
|
612
699
|
Converts a storage tree to its server-side counterpart. Available from `@yakocloud/state-vocab/server`.
|
package/dist/client.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),v=require("./constants-CbsduCZ7.js"),a=require("./utils-0CTNJ4ZE.js"),
|
|
1
|
+
"use client";"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const u=require("react"),v=require("./constants-CbsduCZ7.js"),a=require("./utils-0CTNJ4ZE.js"),V=require("./provider-client-B4XQ24JB.js");function D(t,e,n=[]){return u.useMemo(()=>a.debounce(t,e),n)}const g=t=>{const{vocabStore:e,storage:n,statePath:o,value:i,deserialize:S,serialize:l}=t,f=n.getItem(o);f===null?a.isValueDefined(i)&&n.setItem(o,l(i)):e.set(o,S(f))},z=typeof window>"u",O=z?u.useEffect:u.useLayoutEffect,m=new V.VocabStore,A="Make sure your component is wrapped in StateVocabProvider (RSC) or disable ssr option in setupStorage for SPA (RCC only)";function I(t){t??={};const e=z?void 0:a.valueOrFactory(t.storage),n=a.valueOrFactory(t.defaultValue),o=t.bidirectional,i=this[v.STATE_PATH],S=this[v.STATE_VERBOSE],l=this[v.STATE_VERBOSE_PATH],f=this[v.STATE_SSR];let s=V.useStateVocabClientContext({verbose:S});if(!(s instanceof V.VocabStore)){if(f)throw new Error(A);s=m}const d=t.serialize??JSON.stringify,c=t.deserialize??JSON.parse,E=D(t.onSet??(()=>{}),t.delayedSet,[]),y=u.useRef(void 0),C=u.useRef(!1);if(!C.current){C.current=!0;let r=s.get(i);a.isValueDefined(r)||(r=n,a.isValueDefined(r)&&s.set(i,r)),!f&&e&&g({vocabStore:s,storage:e,statePath:i,value:r,serialize:d,deserialize:c})}const h=u.useSyncExternalStore(s.subscribe.bind(s),s.getClientSnapshot.bind(s),s.getServerSnapshot.bind(s));if(S)if(l){const r=a.get(h,l);r&&a.logStyled(r)}else a.logStyled(h);const T=a.get(h,i,n);y.current=T,O(()=>{!f||!e||g({vocabStore:s,storage:e,statePath:i,value:T,serialize:d,deserialize:c})},[]);const w=u.useEffectEvent(r=>{if(r.key!==i)return;const b=r.newValue,R=(b===null?null:c(b))??n;a.isValueDefined(R)&&(s.set(i,R),E(R,y.current))});u.useEffect(()=>{if(o)return window.addEventListener("storage",w),()=>window.removeEventListener("storage",w)},[o]);const p=u.useCallback(r=>{const b=a.isTransformer(r)?r(y.current):r;s.set(i,b),E(b,y.current),e&&e.setItem(i,d(b))},[s,i,E,e,d]),_=u.useCallback(()=>{const r=n;if(!a.isValueDefined(r)){e?.removeItem(i);return}s.set(i,r),E(r,y.current),e&&e.setItem(i,d(r))},[n,s,i,E,e,d]);return[T,p,_]}function k(t){t??={};const e=z?void 0:a.valueOrFactory(t.storage),n=a.valueOrFactory(t.defaultValue),o=this[v.STATE_PATH],i=this[v.STATE_VERBOSE],S=this[v.STATE_SSR];let l=V.useStateVocabClientContext({verbose:i});if(!(l instanceof V.VocabStore)){if(S)throw new Error(A);l=m}const f=t.serialize??JSON.stringify,s=t.deserialize??JSON.parse,d=u.useRef(!1);let c;d.current||(d.current=!0,c=l.get(o),a.isValueDefined(c)||(c=n,a.isValueDefined(c)&&l.set(o,c)),!S&&e&&g({vocabStore:l,storage:e,statePath:o,value:c,serialize:f,deserialize:s})),O(()=>{!S||!e||g({vocabStore:l,storage:e,statePath:o,value:c,serialize:f,deserialize:s})},[])}function q(t){return typeof t=="object"&&t!==null&&"clientSlot"in t}function P(t){const e={};for(const n in t){const o=t[n];q(o)?(e[n]=o.clientSlot({useState(...i){return I.apply(this,i)},useInitialState(...i){k.apply(this,i)}}),delete e[n].serverSlot,delete e[n].clientSlot):o!==null&&typeof o=="object"?e[n]=P(o):e[n]=o}return e}exports.StateVocabClientProvider=V.StateVocabClientProvider;exports.clientify=P;
|
package/dist/client.es.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { useMemo as
|
|
3
|
-
import { S as
|
|
4
|
-
import { a as d, d as G, v as g, g as
|
|
2
|
+
import { useMemo as x, useRef as y, useSyncExternalStore as B, useEffectEvent as D, useEffect as A, useCallback as T, useLayoutEffect as H } from "react";
|
|
3
|
+
import { S as I, a as O, b as _, c as F } from "./constants-BB1YAX6c.mjs";
|
|
4
|
+
import { a as d, d as G, v as g, g as C, l as P, i as q } from "./utils-xV3x3fTc.mjs";
|
|
5
5
|
import { V as z, u as k } from "./provider-client-C2Aw0Lmi.mjs";
|
|
6
|
+
import { S as ie } from "./provider-client-C2Aw0Lmi.mjs";
|
|
6
7
|
function K(t, e, n = []) {
|
|
7
|
-
return
|
|
8
|
+
return x(
|
|
8
9
|
() => G(t, e),
|
|
9
10
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
10
11
|
n
|
|
@@ -20,10 +21,10 @@ const E = (t) => {
|
|
|
20
21
|
serialize: a
|
|
21
22
|
} = t, c = n.getItem(o);
|
|
22
23
|
c === null ? d(s) && n.setItem(o, a(s)) : e.set(o, f(c));
|
|
23
|
-
},
|
|
24
|
+
}, p = typeof window > "u", J = p ? A : H, L = new z(), N = "Make sure your component is wrapped in StateVocabProvider (RSC) or disable ssr option in setupStorage for SPA (RCC only)";
|
|
24
25
|
function Q(t) {
|
|
25
26
|
t ??= {};
|
|
26
|
-
const e =
|
|
27
|
+
const e = p ? void 0 : g(t.storage), n = g(t.defaultValue), o = t.bidirectional, s = this[I], f = this[O], a = this[F], c = this[_];
|
|
27
28
|
let r = k({ verbose: f });
|
|
28
29
|
if (!(r instanceof z)) {
|
|
29
30
|
if (c)
|
|
@@ -35,9 +36,9 @@ function Q(t) {
|
|
|
35
36
|
}),
|
|
36
37
|
t.delayedSet,
|
|
37
38
|
[]
|
|
38
|
-
), b =
|
|
39
|
-
if (!
|
|
40
|
-
|
|
39
|
+
), b = y(void 0), w = y(!1);
|
|
40
|
+
if (!w.current) {
|
|
41
|
+
w.current = !0;
|
|
41
42
|
let i = r.get(s);
|
|
42
43
|
d(i) || (i = n, d(i) && r.set(s, i)), !c && e && E({
|
|
43
44
|
vocabStore: r,
|
|
@@ -48,18 +49,18 @@ function Q(t) {
|
|
|
48
49
|
deserialize: l
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
|
-
const h =
|
|
52
|
+
const h = B(
|
|
52
53
|
r.subscribe.bind(r),
|
|
53
54
|
r.getClientSnapshot.bind(r),
|
|
54
55
|
r.getServerSnapshot.bind(r)
|
|
55
56
|
);
|
|
56
57
|
if (f)
|
|
57
58
|
if (a) {
|
|
58
|
-
const i =
|
|
59
|
-
i &&
|
|
59
|
+
const i = C(h, a);
|
|
60
|
+
i && P(i);
|
|
60
61
|
} else
|
|
61
|
-
|
|
62
|
-
const m =
|
|
62
|
+
P(h);
|
|
63
|
+
const m = C(h, s, n);
|
|
63
64
|
b.current = m, J(() => {
|
|
64
65
|
!c || !e || E({
|
|
65
66
|
vocabStore: r,
|
|
@@ -70,13 +71,13 @@ function Q(t) {
|
|
|
70
71
|
deserialize: l
|
|
71
72
|
});
|
|
72
73
|
}, []);
|
|
73
|
-
const R =
|
|
74
|
+
const R = D((i) => {
|
|
74
75
|
if (i.key !== s)
|
|
75
76
|
return;
|
|
76
|
-
const S = i.newValue,
|
|
77
|
-
d(
|
|
77
|
+
const S = i.newValue, V = (S === null ? null : l(S)) ?? n;
|
|
78
|
+
d(V) && (r.set(s, V), v(V, b.current));
|
|
78
79
|
});
|
|
79
|
-
|
|
80
|
+
A(() => {
|
|
80
81
|
if (o)
|
|
81
82
|
return window.addEventListener("storage", R), () => window.removeEventListener("storage", R);
|
|
82
83
|
}, [o]);
|
|
@@ -99,14 +100,14 @@ function Q(t) {
|
|
|
99
100
|
}
|
|
100
101
|
function U(t) {
|
|
101
102
|
t ??= {};
|
|
102
|
-
const e =
|
|
103
|
+
const e = p ? void 0 : g(t.storage), n = g(t.defaultValue), o = this[I], s = this[O], f = this[_];
|
|
103
104
|
let a = k({ verbose: s });
|
|
104
105
|
if (!(a instanceof z)) {
|
|
105
106
|
if (f)
|
|
106
107
|
throw new Error(N);
|
|
107
108
|
a = L;
|
|
108
109
|
}
|
|
109
|
-
const c = t.serialize ?? JSON.stringify, r = t.deserialize ?? JSON.parse, u =
|
|
110
|
+
const c = t.serialize ?? JSON.stringify, r = t.deserialize ?? JSON.parse, u = y(!1);
|
|
110
111
|
let l;
|
|
111
112
|
u.current || (u.current = !0, l = a.get(o), d(l) || (l = n, d(l) && a.set(o, l)), !f && e && E({
|
|
112
113
|
vocabStore: a,
|
|
@@ -145,5 +146,6 @@ function X(t) {
|
|
|
145
146
|
return e;
|
|
146
147
|
}
|
|
147
148
|
export {
|
|
149
|
+
ie as StateVocabClientProvider,
|
|
148
150
|
X as clientify
|
|
149
151
|
};
|
package/dist/types/client.d.ts
CHANGED