@kitr/state 0.3.1 → 0.3.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/dist/index.d.ts +7 -1
- package/dist/index.js +10 -3
- package/dist/kitr.d.ts +1 -0
- package/dist/kitr.js +11 -2
- package/docs/api.md +3 -2
- package/docs/index.md +15 -3
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -10,11 +10,17 @@ export interface KitrStoreOptions<T> {
|
|
|
10
10
|
persist?: Partial<PersistOptions<T, Partial<T>>> | boolean;
|
|
11
11
|
/** Connect to the Redux DevTools extension in development. Default `true`; never in production or on the server. */
|
|
12
12
|
devtools?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Throw when a component reads the store on the server outside its `<StoreProvider>`, instead of
|
|
15
|
+
* rendering a fresh copy of the initial state. Default `false`.
|
|
16
|
+
*/
|
|
17
|
+
strict?: boolean;
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* An SSR-safe Zustand store factory. On the client the store is a module-level
|
|
16
21
|
* singleton; on the server (or in tests) you get a fresh store per `<StoreProvider>`,
|
|
17
|
-
* so one user's state never leaks into another's render.
|
|
22
|
+
* so one user's state never leaks into another's render. Without a provider, each component
|
|
23
|
+
* that reads the store on the server gets its own fresh store, built from the initial state.
|
|
18
24
|
*
|
|
19
25
|
* const [useCounter, CounterProvider, counterStore] = createKitrStore({ name: 'counter' }, (set) => ({ ... }))
|
|
20
26
|
*/
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@ export * from "zustand";
|
|
|
7
7
|
/**
|
|
8
8
|
* An SSR-safe Zustand store factory. On the client the store is a module-level
|
|
9
9
|
* singleton; on the server (or in tests) you get a fresh store per `<StoreProvider>`,
|
|
10
|
-
* so one user's state never leaks into another's render.
|
|
10
|
+
* so one user's state never leaks into another's render. Without a provider, each component
|
|
11
|
+
* that reads the store on the server gets its own fresh store, built from the initial state.
|
|
11
12
|
*
|
|
12
13
|
* const [useCounter, CounterProvider, counterStore] = createKitrStore({ name: 'counter' }, (set) => ({ ... }))
|
|
13
14
|
*/
|
|
@@ -54,8 +55,14 @@ function createKitrStore(options, initializer) {
|
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
function useKitrStore(selector) {
|
|
57
|
-
const
|
|
58
|
-
|
|
58
|
+
const fromContext = useContext(Context);
|
|
59
|
+
const own = useRef(null);
|
|
60
|
+
let store = fromContext ?? singleton;
|
|
61
|
+
if (!store) {
|
|
62
|
+
if (options.strict) throw new Error(`[@kitr/state] Store "${options.name}" has no instance on the server. Wrap the tree in its <StoreProvider>.`);
|
|
63
|
+
own.current ??= build();
|
|
64
|
+
store = own.current;
|
|
65
|
+
}
|
|
59
66
|
const select = selector ?? ((s) => s);
|
|
60
67
|
const snapshot = () => select(store.getState());
|
|
61
68
|
const serverSnapshot = options.persist && store === singleton ? () => select(renderedOnServer(store)) : snapshot;
|
package/dist/kitr.d.ts
CHANGED
package/dist/kitr.js
CHANGED
|
@@ -7,7 +7,7 @@ var kitr_default = definePackage({
|
|
|
7
7
|
add(ctx) {
|
|
8
8
|
ctx.deps.add("zustand");
|
|
9
9
|
ctx.write("app/stores/.gitkeep", "");
|
|
10
|
-
ctx.agents.add("state", `- Client state only. \`kitr make:store <name>\` writes \`app/stores/<name>.ts\` using \`createKitrStore\`. Server data belongs in queries, not stores.`);
|
|
10
|
+
ctx.agents.add("state", `- Client state only. \`kitr make:store <name>\` writes \`app/stores/<name>.ts\` using \`createKitrStore\`. On the server, wrap the components that use it in its \`<NameStoreProvider>\` to share one store per render; without it each component renders the initial state. Server data belongs in queries, not stores.`);
|
|
11
11
|
},
|
|
12
12
|
remove(ctx) {
|
|
13
13
|
ctx.deps.remove("zustand");
|
|
@@ -25,6 +25,11 @@ interface ${naming.pascal(name)}State {
|
|
|
25
25
|
reset: () => void
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* In the browser \`use${naming.pascal(name)}Store()\` needs no provider. On the server, wrap the
|
|
30
|
+
* components that call it in \`<${naming.pascal(name)}StoreProvider>\` so they share one store per
|
|
31
|
+
* render; without it each one renders the initial state.
|
|
32
|
+
*/
|
|
28
33
|
export const [use${naming.pascal(name)}Store, ${naming.pascal(name)}StoreProvider, ${naming.camel(name)}Store] = createKitrStore<${naming.pascal(name)}State>(
|
|
29
34
|
{ name: '${naming.snake(name)}', persist: false },
|
|
30
35
|
(set) => ({
|
|
@@ -33,7 +38,11 @@ export const [use${naming.pascal(name)}Store, ${naming.pascal(name)}StoreProvide
|
|
|
33
38
|
reset: () => set({ count: 0 }),
|
|
34
39
|
}),
|
|
35
40
|
)
|
|
36
|
-
|
|
41
|
+
`,
|
|
42
|
+
after(ctx) {
|
|
43
|
+
const keep = `${ctx.rc.directories.app ?? "app"}/stores/.gitkeep`;
|
|
44
|
+
if (ctx.exists(keep)) ctx.unlink(keep);
|
|
45
|
+
}
|
|
37
46
|
} },
|
|
38
47
|
wraps: { zustand: "@kitr/state" },
|
|
39
48
|
eject: { "@kitr/state": "zustand" },
|
package/docs/api.md
CHANGED
|
@@ -26,6 +26,7 @@ Creates a store that is a singleton in the browser and per-provider on the serve
|
|
|
26
26
|
| `name` | `string` | required | Used for devtools and as the storage key `kitr:<name>`. |
|
|
27
27
|
| `persist` | `boolean \| PersistOptions` | `false` | Save to `localStorage`. An object is passed to Zustand's `persist` middleware; its `name` overrides the storage key. |
|
|
28
28
|
| `devtools` | `boolean` | `true` in dev | Connect to Redux DevTools in development. |
|
|
29
|
+
| `strict` | `boolean` | `false` | Throw on the server when the hook runs outside a `StoreProvider`. |
|
|
29
30
|
|
|
30
31
|
### Returns
|
|
31
32
|
|
|
@@ -34,12 +35,12 @@ A tuple. Name the parts whatever suits your store.
|
|
|
34
35
|
| Position | Value | Description |
|
|
35
36
|
| --- | --- | --- |
|
|
36
37
|
| 0 | `useStore()` / `useStore(selector)` | Hook. Without a selector it returns the whole state. |
|
|
37
|
-
| 1 | `StoreProvider` | Component with `children` and optional `initialState: Partial<T>`.
|
|
38
|
+
| 1 | `StoreProvider` | Component with `children` and optional `initialState: Partial<T>`. Shares one store per server render. |
|
|
38
39
|
| 2 | `store` | The browser's `StoreApi<T>`, or `null` on the server. |
|
|
39
40
|
|
|
40
41
|
### Errors
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
Without a surrounding `StoreProvider`, the hook on the server reads a fresh store of the component's own, built from the initial state. With `strict: true` it throws instead:
|
|
43
44
|
|
|
44
45
|
```
|
|
45
46
|
[@kitr/state] Store "cart" has no instance on the server. Wrap the tree in its <StoreProvider>.
|
package/docs/index.md
CHANGED
|
@@ -93,13 +93,14 @@ export const [usePreferences, PreferencesProvider, preferencesStore] =
|
|
|
93
93
|
| `persist.version` | `0` | The saved shape's version. Saved data with another version goes through `migrate`, or is dropped without one. |
|
|
94
94
|
| `persist.migrate` | none | A function `(saved, version) => state` that upgrades old saved data. |
|
|
95
95
|
| `devtools` | `true` | Connects to the Redux DevTools in development; `false` leaves it out. |
|
|
96
|
+
| `strict` | `false` | Throws on the server when a component reads the store outside its `StoreProvider`. |
|
|
96
97
|
|
|
97
98
|
The second argument is a Zustand state creator: it gets `set`, `get` and the store, and returns the initial state with its actions. `createKitrStore` returns three things, in this order:
|
|
98
99
|
|
|
99
100
|
| Value | Description |
|
|
100
101
|
| --- | --- |
|
|
101
102
|
| `useStore` | The hook. `useStore(selector)` returns a slice; `useStore()` returns the whole state. |
|
|
102
|
-
| `StoreProvider` | A component that gives its subtree a store.
|
|
103
|
+
| `StoreProvider` | A component that gives its subtree a store. Shares one store per server render; optional in the browser. Takes `initialState`. |
|
|
103
104
|
| `store` | The browser's store instance, for code outside components. `null` on the server. |
|
|
104
105
|
|
|
105
106
|
|
|
@@ -457,8 +458,13 @@ One server process renders pages for many users, so a module-level store would l
|
|
|
457
458
|
|
|
458
459
|
- **In the browser**, the store is one module-level instance, read without a provider.
|
|
459
460
|
- **On the server**, each `<StoreProvider>` creates a fresh store for its render, and storage is never touched.
|
|
461
|
+
- **On the server without a provider**, each component that reads the store gets its own fresh store, built from the initial state and dropped with the render.
|
|
460
462
|
|
|
461
|
-
|
|
463
|
+
So a page renders on the server with or without a provider, and no state outlives the request. Without a provider nothing is shared either: a write during the server render reaches only the component that made it. Effects and event handlers run in the browser, where the one store is shared as usual.
|
|
464
|
+
|
|
465
|
+
### Static and SSR
|
|
466
|
+
|
|
467
|
+
To share one store across a server render, for example to seed it with `initialState`, wrap the part of the tree that reads it:
|
|
462
468
|
|
|
463
469
|
```tsx
|
|
464
470
|
// app/document.tsx
|
|
@@ -482,7 +488,9 @@ export function RootDocument() {
|
|
|
482
488
|
}
|
|
483
489
|
```
|
|
484
490
|
|
|
485
|
-
|
|
491
|
+
In a client-only app (`spa` mode, desktop, mobile) skip the provider.
|
|
492
|
+
|
|
493
|
+
To make a missing provider an error instead, pass `strict: true`. The server render then fails with:
|
|
486
494
|
|
|
487
495
|
```txt
|
|
488
496
|
[@kitr/state] Store "order-draft" has no instance on the server. Wrap the tree in its <StoreProvider>.
|
|
@@ -490,6 +498,10 @@ Without a provider, the server render fails with:
|
|
|
490
498
|
|
|
491
499
|
In a client-only app (`spa` mode, desktop, mobile) skip the provider.
|
|
492
500
|
|
|
501
|
+
### Static and SSR
|
|
502
|
+
|
|
503
|
+
A `static` site is rendered on the server too: `kitr build` prerenders every page there, so the same rules apply as in an `ssr` app. Without a provider each component that reads the store gets its own fresh copy of the initial state for that render, which is fine for a widget that owns its state and wrong for two components that are meant to share it. To share, wrap them in one line in `app/document.tsx`, `<OrderDraftProvider><Outlet /></OrderDraftProvider>`; it is harmless in the browser, where the provider is optional. Set `strict: true` on the store to get an error instead of the fallback.
|
|
504
|
+
|
|
493
505
|
<details>
|
|
494
506
|
<summary>Persisted stores and hydration</summary>
|
|
495
507
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kitr/state",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "@kitr/state: Zustand re-exported, plus an SSR-safe store factory.",
|
|
5
5
|
"kitr": {
|
|
6
6
|
"name": "state",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"docs"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@kitr/core": "0.3.
|
|
47
|
+
"@kitr/core": "0.3.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"react": ">=18.0.0",
|