@pitboxdev/dynamic-store-zustand 0.0.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/LICENSE +21 -0
- package/README.md +482 -0
- package/dist/index.d.mts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pitboxdev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
# @pitboxdev/dynamic-store-zustand
|
|
2
|
+
|
|
3
|
+
> Two complementary approaches to scalable, type-safe state management in React — both built on top of [Zustand](https://github.com/pmndrs/zustand).
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@pitboxdev/dynamic-store-zustand)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Overview](#overview)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [API 1 — `createDynamicStore`](#api-1--createdynamicstore)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Outside React](#outside-react)
|
|
18
|
+
- [TypeScript](#typescript)
|
|
19
|
+
- [API 2 — `useDynamicStore`](#api-2--usedynamicstore)
|
|
20
|
+
- [Quick Start](#quick-start-1)
|
|
21
|
+
- [Functional updater (`setData`)](#functional-updater-setdata)
|
|
22
|
+
- [Auto-cleanup with `useDynamicStoreWithCleanup`](#auto-cleanup-with-usedynamicstorewithcleanup)
|
|
23
|
+
- [Imperative helpers (outside React)](#imperative-helpers-outside-react)
|
|
24
|
+
- [Config options](#config-options)
|
|
25
|
+
- [TypeScript](#typescript-1)
|
|
26
|
+
- [When to use which API](#when-to-use-which-api)
|
|
27
|
+
- [Full API Reference](#full-api-reference)
|
|
28
|
+
- [License](#license)
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Overview
|
|
33
|
+
|
|
34
|
+
The package ships **two independent APIs** that can be used separately or together:
|
|
35
|
+
|
|
36
|
+
| | `createDynamicStore` | `useDynamicStore` |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| Style | Factory function | React hook |
|
|
39
|
+
| Stores | One Zustand store per call | All stores live in a single registry |
|
|
40
|
+
| Actions | Explicit, typed | Implicit via `setData` |
|
|
41
|
+
| Functional updater | Via `set((s) => …)` in actions | Built-in `setData((prev) => …)` |
|
|
42
|
+
| Auto-cleanup | — | `useDynamicStoreWithCleanup` |
|
|
43
|
+
| Navigation persistence | — | `persistOnNavigation` config flag |
|
|
44
|
+
| Best for | Permanent, feature-level stores | Page/form/modal scoped state |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install @pitboxdev/dynamic-store-zustand zustand
|
|
52
|
+
# or
|
|
53
|
+
yarn add @pitboxdev/dynamic-store-zustand zustand
|
|
54
|
+
# or
|
|
55
|
+
pnpm add @pitboxdev/dynamic-store-zustand zustand
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> **Peer dependencies:** `react >= 18` and `zustand >= 5` must be installed in your project.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## API 1 — `createDynamicStore`
|
|
63
|
+
|
|
64
|
+
A factory function that creates a standalone, fully typed Zustand store from a configuration object with explicit actions.
|
|
65
|
+
|
|
66
|
+
### Quick Start
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { createDynamicStore } from "@pitboxdev/dynamic-store-zustand";
|
|
70
|
+
|
|
71
|
+
const { useStore } = createDynamicStore({
|
|
72
|
+
initialState: { count: 0 },
|
|
73
|
+
actions: (set) => ({
|
|
74
|
+
increment: () => set((s) => ({ count: s.count + 1 })),
|
|
75
|
+
decrement: () => set((s) => ({ count: s.count - 1 })),
|
|
76
|
+
reset: () => set({ count: 0 }),
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
function Counter() {
|
|
81
|
+
const count = useStore((s) => s.count);
|
|
82
|
+
const { increment, decrement, reset } = useStore();
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
<button onClick={decrement}>-</button>
|
|
87
|
+
<span>{count}</span>
|
|
88
|
+
<button onClick={increment}>+</button>
|
|
89
|
+
<button onClick={reset}>Reset</button>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Outside React
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
const { store } = createDynamicStore({ initialState: { count: 0 }, actions: (set) => ({
|
|
99
|
+
increment: () => set((s) => ({ count: s.count + 1 })),
|
|
100
|
+
}) });
|
|
101
|
+
|
|
102
|
+
// Read
|
|
103
|
+
const count = store.getState().count;
|
|
104
|
+
|
|
105
|
+
// Write
|
|
106
|
+
store.setState({ count: 42 });
|
|
107
|
+
|
|
108
|
+
// Subscribe
|
|
109
|
+
const unsub = store.subscribe((state) => console.log(state.count));
|
|
110
|
+
unsub();
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### TypeScript
|
|
114
|
+
|
|
115
|
+
All types are inferred automatically. You can also define them explicitly:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import {
|
|
119
|
+
createDynamicStore,
|
|
120
|
+
type DynamicStoreConfig,
|
|
121
|
+
} from "@pitboxdev/dynamic-store-zustand";
|
|
122
|
+
|
|
123
|
+
interface CounterState { count: number }
|
|
124
|
+
interface CounterActions {
|
|
125
|
+
increment: () => void;
|
|
126
|
+
decrement: () => void;
|
|
127
|
+
reset: () => void;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const config: DynamicStoreConfig<CounterState, CounterActions> = {
|
|
131
|
+
initialState: { count: 0 },
|
|
132
|
+
actions: (set) => ({
|
|
133
|
+
increment: () => set((s) => ({ count: s.count + 1 })),
|
|
134
|
+
decrement: () => set((s) => ({ count: s.count - 1 })),
|
|
135
|
+
reset: () => set({ count: 0 }),
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const { useStore, store } = createDynamicStore(config);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Using multiple selectors with shallow equality:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { useShallow } from "zustand/react/shallow";
|
|
146
|
+
|
|
147
|
+
const { count, increment } = useStore(
|
|
148
|
+
useShallow((s) => ({ count: s.count, increment: s.increment }))
|
|
149
|
+
);
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## API 2 — `useDynamicStore`
|
|
155
|
+
|
|
156
|
+
A hook that stores state in a single shared registry keyed by a string `storeId`. `setData` works exactly like React's `useState` setter — it accepts either a partial object or a function that receives the previous state.
|
|
157
|
+
|
|
158
|
+
### Quick Start
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { useDynamicStore } from "@pitboxdev/dynamic-store-zustand";
|
|
162
|
+
|
|
163
|
+
interface CounterState {
|
|
164
|
+
value: number;
|
|
165
|
+
step: number;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const initial: CounterState = { value: 0, step: 1 };
|
|
169
|
+
|
|
170
|
+
function Counter() {
|
|
171
|
+
const { data, setData, reset } = useDynamicStore<CounterState>("counter", {
|
|
172
|
+
initialState: initial,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<div>
|
|
177
|
+
<p>Count: {data.value}</p>
|
|
178
|
+
|
|
179
|
+
{/* Object update */}
|
|
180
|
+
<button onClick={() => setData({ value: data.value + data.step })}>
|
|
181
|
+
+ (simple)
|
|
182
|
+
</button>
|
|
183
|
+
|
|
184
|
+
{/* Functional update — always reads the latest state */}
|
|
185
|
+
<button onClick={() => setData((prev) => ({ value: prev.value + prev.step }))}>
|
|
186
|
+
+ (functional)
|
|
187
|
+
</button>
|
|
188
|
+
|
|
189
|
+
<button onClick={reset}>Reset</button>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Functional updater (`setData`)
|
|
196
|
+
|
|
197
|
+
`setData` accepts two forms:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// 1. Partial object — merges into current state
|
|
201
|
+
setData({ value: 42 });
|
|
202
|
+
|
|
203
|
+
// 2. Updater function — receives the latest state, returns a partial update
|
|
204
|
+
setData((prev) => ({ value: prev.value + 1 }));
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Why use the functional form?**
|
|
208
|
+
|
|
209
|
+
Without it, rapid successive calls all see the same snapshot:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
// ❌ race condition — both calls read the same stale value
|
|
213
|
+
setData({ value: data.value + 1 });
|
|
214
|
+
setData({ value: data.value + 1 }); // data.value is still the old value
|
|
215
|
+
|
|
216
|
+
// ✅ functional — each call receives the result of the previous one
|
|
217
|
+
setData((prev) => ({ value: prev.value + 1 }));
|
|
218
|
+
setData((prev) => ({ value: prev.value + 1 }));
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Always prefer the functional form when the new state depends on the old state.
|
|
222
|
+
|
|
223
|
+
#### Todo list example
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
interface Todo { id: string; text: string; done: boolean }
|
|
227
|
+
interface TodosState { items: Todo[]; filter: "all" | "active" | "done" }
|
|
228
|
+
|
|
229
|
+
function TodoList() {
|
|
230
|
+
const { data, setData } = useDynamicStore<TodosState>("todos", {
|
|
231
|
+
initialState: { items: [], filter: "all" },
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const addTodo = (text: string) => {
|
|
235
|
+
setData((prev) => ({
|
|
236
|
+
items: [...prev.items, { id: Date.now().toString(), text, done: false }],
|
|
237
|
+
}));
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const toggle = (id: string) => {
|
|
241
|
+
setData((prev) => ({
|
|
242
|
+
items: prev.items.map((t) =>
|
|
243
|
+
t.id === id ? { ...t, done: !t.done } : t
|
|
244
|
+
),
|
|
245
|
+
}));
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const clearDone = () => {
|
|
249
|
+
setData((prev) => ({
|
|
250
|
+
items: prev.items.filter((t) => !t.done),
|
|
251
|
+
filter: "all",
|
|
252
|
+
}));
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// ...
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### Shopping cart example
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
interface CartItem { id: string; name: string; price: number; quantity: number }
|
|
263
|
+
interface CartState { items: CartItem[]; discount: number }
|
|
264
|
+
|
|
265
|
+
function Cart() {
|
|
266
|
+
const { data, setData } = useDynamicStore<CartState>("cart", {
|
|
267
|
+
initialState: { items: [], discount: 0 },
|
|
268
|
+
persistOnNavigation: true,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const addItem = (product: Omit<CartItem, "quantity">) => {
|
|
272
|
+
setData((prev) => {
|
|
273
|
+
const exists = prev.items.find((i) => i.id === product.id);
|
|
274
|
+
return {
|
|
275
|
+
items: exists
|
|
276
|
+
? prev.items.map((i) =>
|
|
277
|
+
i.id === product.id ? { ...i, quantity: i.quantity + 1 } : i
|
|
278
|
+
)
|
|
279
|
+
: [...prev.items, { ...product, quantity: 1 }],
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const total = data.items.reduce(
|
|
285
|
+
(sum, i) => sum + i.price * i.quantity,
|
|
286
|
+
0
|
|
287
|
+
) * (1 - data.discount / 100);
|
|
288
|
+
|
|
289
|
+
// ...
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Auto-cleanup with `useDynamicStoreWithCleanup`
|
|
294
|
+
|
|
295
|
+
`useDynamicStoreWithCleanup` works identically to `useDynamicStore` but resets the store when the component unmounts — useful for modal dialogs, wizard steps, or edit forms.
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
import { useDynamicStoreWithCleanup } from "@pitboxdev/dynamic-store-zustand";
|
|
299
|
+
|
|
300
|
+
function EditModal() {
|
|
301
|
+
const { data, setData } = useDynamicStoreWithCleanup<FormState>(
|
|
302
|
+
"editForm",
|
|
303
|
+
{ initialState: { name: "", email: "" }, resetOnUnmount: true }
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// State is automatically reset when the modal closes
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Imperative helpers (outside React)
|
|
311
|
+
|
|
312
|
+
All helpers call into the shared manager directly — no hook required.
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
import {
|
|
316
|
+
updateDynamicStore,
|
|
317
|
+
resetDynamicStore,
|
|
318
|
+
resetAllDynamicStores,
|
|
319
|
+
resetNonPersistentDynamicStores,
|
|
320
|
+
} from "@pitboxdev/dynamic-store-zustand";
|
|
321
|
+
|
|
322
|
+
// Merge data into a store
|
|
323
|
+
updateDynamicStore("cart", { discount: 20 });
|
|
324
|
+
|
|
325
|
+
// Reset one store to its initial state
|
|
326
|
+
resetDynamicStore("editForm");
|
|
327
|
+
|
|
328
|
+
// Reset every store
|
|
329
|
+
resetAllDynamicStores();
|
|
330
|
+
|
|
331
|
+
// Reset only stores without persistOnNavigation: true (e.g. on route change)
|
|
332
|
+
resetNonPersistentDynamicStores();
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Config options
|
|
336
|
+
|
|
337
|
+
| Option | Type | Default | Description |
|
|
338
|
+
|---|---|---|---|
|
|
339
|
+
| `initialState` | `T` | `{}` | Initial values; also used when `reset()` is called |
|
|
340
|
+
| `persistOnNavigation` | `boolean` | `false` | Skip reset when `resetNonPersistentDynamicStores()` is called |
|
|
341
|
+
| `resetOnUnmount` | `boolean` | `false` | Auto-reset when the component unmounts (`useDynamicStoreWithCleanup` only) |
|
|
342
|
+
|
|
343
|
+
### TypeScript
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
import {
|
|
347
|
+
useDynamicStore,
|
|
348
|
+
type StoreConfig,
|
|
349
|
+
type SetStateAction,
|
|
350
|
+
type UseDynamicStoreReturn,
|
|
351
|
+
} from "@pitboxdev/dynamic-store-zustand";
|
|
352
|
+
|
|
353
|
+
interface FormState {
|
|
354
|
+
firstName: string;
|
|
355
|
+
lastName: string;
|
|
356
|
+
age: number;
|
|
357
|
+
agreed: boolean;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const config: StoreConfig<FormState> = {
|
|
361
|
+
initialState: { firstName: "", lastName: "", age: 0, agreed: false },
|
|
362
|
+
resetOnUnmount: true,
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
function RegistrationForm() {
|
|
366
|
+
const { data, setData, reset }: UseDynamicStoreReturn<FormState> =
|
|
367
|
+
useDynamicStore<FormState>("regForm", config);
|
|
368
|
+
|
|
369
|
+
// TypeScript will error on unknown keys:
|
|
370
|
+
// setData({ unknown: true }); ❌
|
|
371
|
+
|
|
372
|
+
const setAge = (age: number) => {
|
|
373
|
+
setData((prev) => ({
|
|
374
|
+
age,
|
|
375
|
+
// Clear consent when user is under 18
|
|
376
|
+
agreed: age < 18 ? false : prev.agreed,
|
|
377
|
+
}));
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// ...
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## When to use which API
|
|
387
|
+
|
|
388
|
+
### Use `createDynamicStore` when you need:
|
|
389
|
+
|
|
390
|
+
- A **permanent, feature-level store** (auth, theme, user profile, global UI)
|
|
391
|
+
- **Explicit, named actions** that encapsulate business logic
|
|
392
|
+
- **Fine-grained selectors** and subscriptions outside React
|
|
393
|
+
- The full Zustand API (middleware, persist, devtools, subscribe)
|
|
394
|
+
|
|
395
|
+
```ts
|
|
396
|
+
// auth.store.ts
|
|
397
|
+
export const { useStore: useAuthStore, store: authStore } = createDynamicStore({
|
|
398
|
+
initialState: { user: null as User | null, token: "" },
|
|
399
|
+
actions: (set) => ({
|
|
400
|
+
login: (user: User, token: string) => set({ user, token }),
|
|
401
|
+
logout: () => set({ user: null, token: "" }),
|
|
402
|
+
}),
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Use `useDynamicStore` when you need:
|
|
407
|
+
|
|
408
|
+
- **Page / route / modal scoped state** with optional auto-cleanup
|
|
409
|
+
- **useState-like ergonomics** without boilerplate action definitions
|
|
410
|
+
- **Multiple stores** managed centrally with shared reset helpers
|
|
411
|
+
- Quick iteration when action interfaces aren't stable yet
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
// Inside a wizard step component
|
|
415
|
+
const { data, setData } = useDynamicStoreWithCleanup<StepState>(
|
|
416
|
+
"wizard-step-2",
|
|
417
|
+
{ initialState: { selection: null }, resetOnUnmount: true }
|
|
418
|
+
);
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## Full API Reference
|
|
424
|
+
|
|
425
|
+
### `createDynamicStore(config)`
|
|
426
|
+
|
|
427
|
+
| Parameter | Type | Description |
|
|
428
|
+
|---|---|---|
|
|
429
|
+
| `config.initialState` | `TState` | Plain object representing the initial state |
|
|
430
|
+
| `config.actions` | `(set, get) => TActions` | Factory that returns named action implementations |
|
|
431
|
+
|
|
432
|
+
Returns `{ useStore, store }`.
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
### `useDynamicStore<T>(storeId, config?)`
|
|
437
|
+
|
|
438
|
+
| Parameter | Type | Description |
|
|
439
|
+
|---|---|---|
|
|
440
|
+
| `storeId` | `string` | Unique key identifying this store in the registry |
|
|
441
|
+
| `config` | `StoreConfig<T>` | Optional config (see [Config options](#config-options)) |
|
|
442
|
+
|
|
443
|
+
Returns `{ data: T, setData, reset }`.
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
### `useDynamicStoreWithCleanup<T>(storeId, config?)`
|
|
448
|
+
|
|
449
|
+
Same signature as `useDynamicStore`. Calls `reset()` on component unmount when `config.resetOnUnmount` is `true`.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### Imperative helpers
|
|
454
|
+
|
|
455
|
+
| Function | Signature | Description |
|
|
456
|
+
|---|---|---|
|
|
457
|
+
| `updateDynamicStore` | `(storeId, data) => void` | Merge data into a store from outside React |
|
|
458
|
+
| `resetDynamicStore` | `(storeId) => void` | Reset one store to its `initialState` |
|
|
459
|
+
| `resetAllDynamicStores` | `() => void` | Reset every registered store |
|
|
460
|
+
| `resetNonPersistentDynamicStores` | `() => void` | Reset stores where `persistOnNavigation` is not `true` |
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
### Exported types
|
|
465
|
+
|
|
466
|
+
| Type | Description |
|
|
467
|
+
|---|---|
|
|
468
|
+
| `StoreState` | `Record<string, unknown>` — base constraint for state objects |
|
|
469
|
+
| `StoreActions` | `Record<string, (...args) => unknown>` — base constraint for action maps |
|
|
470
|
+
| `DynamicStoreConfig<TState, TActions>` | Config type for `createDynamicStore` |
|
|
471
|
+
| `DynamicStore<TState, TActions>` | Return type of `createDynamicStore` |
|
|
472
|
+
| `StoreSlice<TState, TActions>` | Merged state + actions type |
|
|
473
|
+
| `StoreConfig<T>` | Config type for `useDynamicStore` |
|
|
474
|
+
| `SetStateAction<T>` | `Partial<T> \| ((prev: T) => Partial<T>)` — setter argument type |
|
|
475
|
+
| `DynamicStoreRegistry` | Internal registry entry (advanced use) |
|
|
476
|
+
| `UseDynamicStoreReturn<T>` | Return type of `useDynamicStore` |
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## License
|
|
481
|
+
|
|
482
|
+
[MIT](./LICENSE) © Pitboxdev
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { UseBoundStore, StoreApi } from 'zustand';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Updater argument for setData — either a partial object or a function
|
|
5
|
+
* that receives the previous state and returns a partial object.
|
|
6
|
+
* Mirrors the React useState updater pattern.
|
|
7
|
+
*/
|
|
8
|
+
type SetStateAction<T> = Partial<T> | ((prevState: T) => Partial<T>);
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for useDynamicStore / useDynamicStoreWithCleanup.
|
|
11
|
+
*/
|
|
12
|
+
interface StoreConfig<T extends StoreState = StoreState> {
|
|
13
|
+
/** Keep state alive across navigation (not reset on resetNonPersistentStores). */
|
|
14
|
+
persistOnNavigation?: boolean;
|
|
15
|
+
/** Automatically reset state when the component unmounts. */
|
|
16
|
+
resetOnUnmount?: boolean;
|
|
17
|
+
/** Initial state values used on first mount and on reset. */
|
|
18
|
+
initialState?: T;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Internal registry entry stored per storeId in the manager.
|
|
22
|
+
*/
|
|
23
|
+
interface DynamicStoreRegistry {
|
|
24
|
+
data: StoreState;
|
|
25
|
+
config: StoreConfig;
|
|
26
|
+
initialState: StoreState;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A plain object that can serve as store state.
|
|
30
|
+
* Keys are strings, values can be anything.
|
|
31
|
+
*/
|
|
32
|
+
type StoreState = Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Actions are functions attached to the store.
|
|
35
|
+
*/
|
|
36
|
+
type StoreActions = Record<string, (...args: unknown[]) => unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Full store slice = state + actions.
|
|
39
|
+
*/
|
|
40
|
+
type StoreSlice<TState extends StoreState = StoreState, TActions extends StoreActions = StoreActions> = TState & TActions;
|
|
41
|
+
/**
|
|
42
|
+
* Configuration object passed to createDynamicStore.
|
|
43
|
+
*/
|
|
44
|
+
interface DynamicStoreConfig<TState extends StoreState, TActions extends StoreActions> {
|
|
45
|
+
/** Initial state values */
|
|
46
|
+
initialState: TState;
|
|
47
|
+
/** Factory that receives `set` and `get` and returns action implementations */
|
|
48
|
+
actions: (set: StoreApi<StoreSlice<TState, TActions>>["setState"], get: StoreApi<StoreSlice<TState, TActions>>["getState"]) => TActions;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The return type of createDynamicStore.
|
|
52
|
+
*/
|
|
53
|
+
interface DynamicStore<TState extends StoreState, TActions extends StoreActions> {
|
|
54
|
+
/** Zustand React hook */
|
|
55
|
+
useStore: UseBoundStore<StoreApi<StoreSlice<TState, TActions>>>;
|
|
56
|
+
/** Direct access to the underlying Zustand store API */
|
|
57
|
+
store: StoreApi<StoreSlice<TState, TActions>>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a dynamic Zustand store from a configuration object.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const { useStore } = createDynamicStore({
|
|
66
|
+
* initialState: { count: 0 },
|
|
67
|
+
* actions: (set) => ({
|
|
68
|
+
* increment: () => set((s) => ({ count: s.count + 1 })),
|
|
69
|
+
* decrement: () => set((s) => ({ count: s.count - 1 })),
|
|
70
|
+
* reset: () => set({ count: 0 }),
|
|
71
|
+
* }),
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function createDynamicStore<TState extends StoreState, TActions extends StoreActions>(config: DynamicStoreConfig<TState, TActions>): DynamicStore<TState, TActions>;
|
|
76
|
+
|
|
77
|
+
interface UseDynamicStoreReturn<T extends StoreState> {
|
|
78
|
+
data: T;
|
|
79
|
+
setData: (updater: SetStateAction<T>) => void;
|
|
80
|
+
reset: () => void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Hook-based dynamic store keyed by `storeId`.
|
|
84
|
+
*
|
|
85
|
+
* `setData` accepts either a partial object **or** a function that receives
|
|
86
|
+
* the previous state and returns a partial object — exactly like React's
|
|
87
|
+
* `useState` setter.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const { data, setData, reset } = useDynamicStore<CounterState>('counter', {
|
|
92
|
+
* initialState: { value: 0, step: 1 },
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // object update
|
|
96
|
+
* setData({ value: 42 });
|
|
97
|
+
*
|
|
98
|
+
* // functional update — safe for rapid successive calls
|
|
99
|
+
* setData((prev) => ({ value: prev.value + prev.step }));
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function useDynamicStore<T extends StoreState>(storeId: string, config?: StoreConfig<T>): UseDynamicStoreReturn<T>;
|
|
103
|
+
/**
|
|
104
|
+
* Same as `useDynamicStore` but automatically resets state when the
|
|
105
|
+
* component unmounts (when `config.resetOnUnmount` is `true`).
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* const { data, setData, reset } = useDynamicStoreWithCleanup<FormState>(
|
|
110
|
+
* 'editForm',
|
|
111
|
+
* { initialState, resetOnUnmount: true },
|
|
112
|
+
* );
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function useDynamicStoreWithCleanup<T extends StoreState>(storeId: string, config?: StoreConfig<T>): UseDynamicStoreReturn<T>;
|
|
116
|
+
/**
|
|
117
|
+
* Update a dynamic store from outside a React component.
|
|
118
|
+
*/
|
|
119
|
+
declare const updateDynamicStore: (storeId: string, data: StoreState) => void;
|
|
120
|
+
/**
|
|
121
|
+
* Reset a single dynamic store to its initial state from outside React.
|
|
122
|
+
*/
|
|
123
|
+
declare const resetDynamicStore: (storeId: string) => void;
|
|
124
|
+
/**
|
|
125
|
+
* Reset all dynamic stores to their initial states from outside React.
|
|
126
|
+
*/
|
|
127
|
+
declare const resetAllDynamicStores: () => void;
|
|
128
|
+
/**
|
|
129
|
+
* Reset only stores that do not have `persistOnNavigation: true`.
|
|
130
|
+
*/
|
|
131
|
+
declare const resetNonPersistentDynamicStores: () => void;
|
|
132
|
+
|
|
133
|
+
export { type DynamicStore, type DynamicStoreConfig, type DynamicStoreRegistry, type SetStateAction, type StoreActions, type StoreConfig, type StoreSlice, type StoreState, type UseDynamicStoreReturn, createDynamicStore, resetAllDynamicStores, resetDynamicStore, resetNonPersistentDynamicStores, updateDynamicStore, useDynamicStore, useDynamicStoreWithCleanup };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { UseBoundStore, StoreApi } from 'zustand';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Updater argument for setData — either a partial object or a function
|
|
5
|
+
* that receives the previous state and returns a partial object.
|
|
6
|
+
* Mirrors the React useState updater pattern.
|
|
7
|
+
*/
|
|
8
|
+
type SetStateAction<T> = Partial<T> | ((prevState: T) => Partial<T>);
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for useDynamicStore / useDynamicStoreWithCleanup.
|
|
11
|
+
*/
|
|
12
|
+
interface StoreConfig<T extends StoreState = StoreState> {
|
|
13
|
+
/** Keep state alive across navigation (not reset on resetNonPersistentStores). */
|
|
14
|
+
persistOnNavigation?: boolean;
|
|
15
|
+
/** Automatically reset state when the component unmounts. */
|
|
16
|
+
resetOnUnmount?: boolean;
|
|
17
|
+
/** Initial state values used on first mount and on reset. */
|
|
18
|
+
initialState?: T;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Internal registry entry stored per storeId in the manager.
|
|
22
|
+
*/
|
|
23
|
+
interface DynamicStoreRegistry {
|
|
24
|
+
data: StoreState;
|
|
25
|
+
config: StoreConfig;
|
|
26
|
+
initialState: StoreState;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A plain object that can serve as store state.
|
|
30
|
+
* Keys are strings, values can be anything.
|
|
31
|
+
*/
|
|
32
|
+
type StoreState = Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* Actions are functions attached to the store.
|
|
35
|
+
*/
|
|
36
|
+
type StoreActions = Record<string, (...args: unknown[]) => unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Full store slice = state + actions.
|
|
39
|
+
*/
|
|
40
|
+
type StoreSlice<TState extends StoreState = StoreState, TActions extends StoreActions = StoreActions> = TState & TActions;
|
|
41
|
+
/**
|
|
42
|
+
* Configuration object passed to createDynamicStore.
|
|
43
|
+
*/
|
|
44
|
+
interface DynamicStoreConfig<TState extends StoreState, TActions extends StoreActions> {
|
|
45
|
+
/** Initial state values */
|
|
46
|
+
initialState: TState;
|
|
47
|
+
/** Factory that receives `set` and `get` and returns action implementations */
|
|
48
|
+
actions: (set: StoreApi<StoreSlice<TState, TActions>>["setState"], get: StoreApi<StoreSlice<TState, TActions>>["getState"]) => TActions;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The return type of createDynamicStore.
|
|
52
|
+
*/
|
|
53
|
+
interface DynamicStore<TState extends StoreState, TActions extends StoreActions> {
|
|
54
|
+
/** Zustand React hook */
|
|
55
|
+
useStore: UseBoundStore<StoreApi<StoreSlice<TState, TActions>>>;
|
|
56
|
+
/** Direct access to the underlying Zustand store API */
|
|
57
|
+
store: StoreApi<StoreSlice<TState, TActions>>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a dynamic Zustand store from a configuration object.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const { useStore } = createDynamicStore({
|
|
66
|
+
* initialState: { count: 0 },
|
|
67
|
+
* actions: (set) => ({
|
|
68
|
+
* increment: () => set((s) => ({ count: s.count + 1 })),
|
|
69
|
+
* decrement: () => set((s) => ({ count: s.count - 1 })),
|
|
70
|
+
* reset: () => set({ count: 0 }),
|
|
71
|
+
* }),
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
declare function createDynamicStore<TState extends StoreState, TActions extends StoreActions>(config: DynamicStoreConfig<TState, TActions>): DynamicStore<TState, TActions>;
|
|
76
|
+
|
|
77
|
+
interface UseDynamicStoreReturn<T extends StoreState> {
|
|
78
|
+
data: T;
|
|
79
|
+
setData: (updater: SetStateAction<T>) => void;
|
|
80
|
+
reset: () => void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Hook-based dynamic store keyed by `storeId`.
|
|
84
|
+
*
|
|
85
|
+
* `setData` accepts either a partial object **or** a function that receives
|
|
86
|
+
* the previous state and returns a partial object — exactly like React's
|
|
87
|
+
* `useState` setter.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const { data, setData, reset } = useDynamicStore<CounterState>('counter', {
|
|
92
|
+
* initialState: { value: 0, step: 1 },
|
|
93
|
+
* });
|
|
94
|
+
*
|
|
95
|
+
* // object update
|
|
96
|
+
* setData({ value: 42 });
|
|
97
|
+
*
|
|
98
|
+
* // functional update — safe for rapid successive calls
|
|
99
|
+
* setData((prev) => ({ value: prev.value + prev.step }));
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function useDynamicStore<T extends StoreState>(storeId: string, config?: StoreConfig<T>): UseDynamicStoreReturn<T>;
|
|
103
|
+
/**
|
|
104
|
+
* Same as `useDynamicStore` but automatically resets state when the
|
|
105
|
+
* component unmounts (when `config.resetOnUnmount` is `true`).
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* const { data, setData, reset } = useDynamicStoreWithCleanup<FormState>(
|
|
110
|
+
* 'editForm',
|
|
111
|
+
* { initialState, resetOnUnmount: true },
|
|
112
|
+
* );
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function useDynamicStoreWithCleanup<T extends StoreState>(storeId: string, config?: StoreConfig<T>): UseDynamicStoreReturn<T>;
|
|
116
|
+
/**
|
|
117
|
+
* Update a dynamic store from outside a React component.
|
|
118
|
+
*/
|
|
119
|
+
declare const updateDynamicStore: (storeId: string, data: StoreState) => void;
|
|
120
|
+
/**
|
|
121
|
+
* Reset a single dynamic store to its initial state from outside React.
|
|
122
|
+
*/
|
|
123
|
+
declare const resetDynamicStore: (storeId: string) => void;
|
|
124
|
+
/**
|
|
125
|
+
* Reset all dynamic stores to their initial states from outside React.
|
|
126
|
+
*/
|
|
127
|
+
declare const resetAllDynamicStores: () => void;
|
|
128
|
+
/**
|
|
129
|
+
* Reset only stores that do not have `persistOnNavigation: true`.
|
|
130
|
+
*/
|
|
131
|
+
declare const resetNonPersistentDynamicStores: () => void;
|
|
132
|
+
|
|
133
|
+
export { type DynamicStore, type DynamicStoreConfig, type DynamicStoreRegistry, type SetStateAction, type StoreActions, type StoreConfig, type StoreSlice, type StoreState, type UseDynamicStoreReturn, createDynamicStore, resetAllDynamicStores, resetDynamicStore, resetNonPersistentDynamicStores, updateDynamicStore, useDynamicStore, useDynamicStoreWithCleanup };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var zustand=require('zustand'),middleware=require('zustand/middleware'),react=require('react');function d(r){let{initialState:t,actions:e}=r,o=zustand.create()((n,s)=>({...t,...e(n,s)}));return {useStore:o,store:o}}var i=zustand.create()(middleware.devtools(r=>({stores:{},setStoreData:(t,e,o)=>{r(n=>{let s=n.stores[t],a={data:{...s?.data??o?.initialState??{},...e},config:o??s?.config??{},initialState:o?.initialState??s?.initialState??{}};return {stores:{...n.stores,[t]:a}}});},resetStore:t=>{r(e=>{let o=e.stores[t];return o?{stores:{...e.stores,[t]:{...o,data:{...o.initialState}}}}:e});},resetAllStores:()=>{r(t=>{let e={};for(let[o,n]of Object.entries(t.stores))e[o]={...n,data:{...n.initialState}};return {stores:e}});},resetNonPersistentStores:()=>{r(t=>{let e={};for(let[o,n]of Object.entries(t.stores))e[o]=n.config.persistOnNavigation===true?n:{...n,data:{...n.initialState}};return {stores:e}});}}),{name:"DynamicStoresManager"}));function c(r,t){let e=i(),o=e.stores[r];return react.useEffect(()=>{!o&&t?.initialState!==void 0&&e.setStoreData(r,{},t);},[r]),{data:o?.data??t?.initialState??{},setData:a=>{if(typeof a=="function"){let y=e.stores[r]?.data??t?.initialState??{},D=a(y);e.setStoreData(r,D,t);}else e.setStoreData(r,a,t);},reset:()=>{e.resetStore(r);}}}function g(r,t){let{data:e,setData:o,reset:n}=c(r,t);return react.useEffect(()=>()=>{t?.resetOnUnmount===true&&n();},[r,t?.resetOnUnmount]),{data:e,setData:o,reset:n}}var l=(r,t)=>{i.getState().setStoreData(r,t);},x=r=>{i.getState().resetStore(r);},T=()=>{i.getState().resetAllStores();},A=()=>{i.getState().resetNonPersistentStores();};exports.createDynamicStore=d;exports.resetAllDynamicStores=T;exports.resetDynamicStore=x;exports.resetNonPersistentDynamicStores=A;exports.updateDynamicStore=l;exports.useDynamicStore=c;exports.useDynamicStoreWithCleanup=g;//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createDynamicStore.ts","../src/dynamicStore.ts"],"names":["createDynamicStore","config","initialState","actions","store","create","set","get","useDynamicStoresManager","devtools","storeId","data","state","existingStore","entry","next","id","useDynamicStore","storesManager","storeRegistry","useEffect","updater","currentData","updates","useDynamicStoreWithCleanup","setData","reset","updateDynamicStore","resetDynamicStore","resetAllDynamicStores","resetNonPersistentDynamicStores"],"mappings":"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","file":"index.js","sourcesContent":["import { create } from \"zustand\";\nimport type {\n DynamicStore,\n DynamicStoreConfig,\n StoreActions,\n StoreState,\n StoreSlice,\n} from \"./types\";\n\n/**\n * Creates a dynamic Zustand store from a configuration object.\n *\n * @example\n * ```ts\n * const { useStore } = createDynamicStore({\n * initialState: { count: 0 },\n * actions: (set) => ({\n * increment: () => set((s) => ({ count: s.count + 1 })),\n * decrement: () => set((s) => ({ count: s.count - 1 })),\n * reset: () => set({ count: 0 }),\n * }),\n * });\n * ```\n */\nexport function createDynamicStore<\n TState extends StoreState,\n TActions extends StoreActions,\n>(\n config: DynamicStoreConfig<TState, TActions>,\n): DynamicStore<TState, TActions> {\n const { initialState, actions } = config;\n\n const store = create<StoreSlice<TState, TActions>>()((set, get) => ({\n ...initialState,\n ...actions(set, get),\n }));\n\n return {\n useStore: store,\n store,\n };\n}\n","import { create } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport { useEffect } from \"react\";\nimport type {\n SetStateAction,\n StoreConfig,\n DynamicStoreRegistry,\n StoreState,\n} from \"./types\";\n\n// ─── Internal manager state ───────────────────────────────────────────────────\n\ninterface DynamicStoresState {\n stores: Record<string, DynamicStoreRegistry>;\n setStoreData: (\n storeId: string,\n data: StoreState,\n config?: StoreConfig,\n ) => void;\n resetStore: (storeId: string) => void;\n resetAllStores: () => void;\n resetNonPersistentStores: () => void;\n}\n\nconst useDynamicStoresManager = create<DynamicStoresState>()(\n devtools(\n (set) => ({\n stores: {},\n\n setStoreData: (storeId, data, config) => {\n set((state) => {\n const existingStore = state.stores[storeId];\n const currentData: StoreState =\n existingStore?.data ?? config?.initialState ?? {};\n\n const entry: DynamicStoreRegistry = {\n data: { ...currentData, ...data },\n config: config ?? existingStore?.config ?? {},\n initialState:\n config?.initialState ?? existingStore?.initialState ?? {},\n };\n\n return {\n stores: { ...state.stores, [storeId]: entry },\n };\n });\n },\n\n resetStore: (storeId) => {\n set((state) => {\n const store = state.stores[storeId];\n if (!store) return state;\n\n return {\n stores: {\n ...state.stores,\n [storeId]: { ...store, data: { ...store.initialState } },\n },\n };\n });\n },\n\n resetAllStores: () => {\n set((state) => {\n const next: Record<string, DynamicStoreRegistry> = {};\n\n for (const [id, store] of Object.entries(state.stores)) {\n next[id] = { ...store, data: { ...store.initialState } };\n }\n\n return { stores: next };\n });\n },\n\n resetNonPersistentStores: () => {\n set((state) => {\n const next: Record<string, DynamicStoreRegistry> = {};\n\n for (const [id, store] of Object.entries(state.stores)) {\n next[id] =\n store.config.persistOnNavigation === true\n ? store\n : { ...store, data: { ...store.initialState } };\n }\n\n return { stores: next };\n });\n },\n }),\n { name: \"DynamicStoresManager\" },\n ),\n);\n\n// ─── Return types ─────────────────────────────────────────────────────────────\n\nexport interface UseDynamicStoreReturn<T extends StoreState> {\n data: T;\n setData: (updater: SetStateAction<T>) => void;\n reset: () => void;\n}\n\n// ─── useDynamicStore ──────────────────────────────────────────────────────────\n\n/**\n * Hook-based dynamic store keyed by `storeId`.\n *\n * `setData` accepts either a partial object **or** a function that receives\n * the previous state and returns a partial object — exactly like React's\n * `useState` setter.\n *\n * @example\n * ```tsx\n * const { data, setData, reset } = useDynamicStore<CounterState>('counter', {\n * initialState: { value: 0, step: 1 },\n * });\n *\n * // object update\n * setData({ value: 42 });\n *\n * // functional update — safe for rapid successive calls\n * setData((prev) => ({ value: prev.value + prev.step }));\n * ```\n */\nexport function useDynamicStore<T extends StoreState>(\n storeId: string,\n config?: StoreConfig<T>,\n): UseDynamicStoreReturn<T> {\n const storesManager = useDynamicStoresManager();\n const storeRegistry = storesManager.stores[storeId];\n\n // Initialize the store entry on first use\n useEffect(() => {\n if (!storeRegistry && config?.initialState !== undefined) {\n storesManager.setStoreData(storeId, {}, config as StoreConfig);\n }\n // Only run on mount / storeId change — intentional dep list\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storeId]);\n\n const data = (storeRegistry?.data ?? config?.initialState ?? {}) as T;\n\n const setData = (updater: SetStateAction<T>): void => {\n if (typeof updater === \"function\") {\n const currentData = (\n storesManager.stores[storeId]?.data ?? config?.initialState ?? {}\n ) as T;\n const updates = updater(currentData);\n storesManager.setStoreData(\n storeId,\n updates as StoreState,\n config as StoreConfig | undefined,\n );\n } else {\n storesManager.setStoreData(\n storeId,\n updater as StoreState,\n config as StoreConfig | undefined,\n );\n }\n };\n\n const reset = (): void => {\n storesManager.resetStore(storeId);\n };\n\n return { data, setData, reset };\n}\n\n// ─── useDynamicStoreWithCleanup ───────────────────────────────────────────────\n\n/**\n * Same as `useDynamicStore` but automatically resets state when the\n * component unmounts (when `config.resetOnUnmount` is `true`).\n *\n * @example\n * ```tsx\n * const { data, setData, reset } = useDynamicStoreWithCleanup<FormState>(\n * 'editForm',\n * { initialState, resetOnUnmount: true },\n * );\n * ```\n */\nexport function useDynamicStoreWithCleanup<T extends StoreState>(\n storeId: string,\n config?: StoreConfig<T>,\n): UseDynamicStoreReturn<T> {\n const { data, setData, reset } = useDynamicStore<T>(storeId, config);\n\n useEffect(() => {\n return () => {\n if (config?.resetOnUnmount === true) {\n reset();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storeId, config?.resetOnUnmount]);\n\n return { data, setData, reset };\n}\n\n// ─── Imperative helpers (outside React) ──────────────────────────────────────\n\n/**\n * Update a dynamic store from outside a React component.\n */\nexport const updateDynamicStore = (\n storeId: string,\n data: StoreState,\n): void => {\n useDynamicStoresManager.getState().setStoreData(storeId, data);\n};\n\n/**\n * Reset a single dynamic store to its initial state from outside React.\n */\nexport const resetDynamicStore = (storeId: string): void => {\n useDynamicStoresManager.getState().resetStore(storeId);\n};\n\n/**\n * Reset all dynamic stores to their initial states from outside React.\n */\nexport const resetAllDynamicStores = (): void => {\n useDynamicStoresManager.getState().resetAllStores();\n};\n\n/**\n * Reset only stores that do not have `persistOnNavigation: true`.\n */\nexport const resetNonPersistentDynamicStores = (): void => {\n useDynamicStoresManager.getState().resetNonPersistentStores();\n};\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {create}from'zustand';import {devtools}from'zustand/middleware';import {useEffect}from'react';function d(r){let{initialState:t,actions:e}=r,o=create()((n,s)=>({...t,...e(n,s)}));return {useStore:o,store:o}}var i=create()(devtools(r=>({stores:{},setStoreData:(t,e,o)=>{r(n=>{let s=n.stores[t],a={data:{...s?.data??o?.initialState??{},...e},config:o??s?.config??{},initialState:o?.initialState??s?.initialState??{}};return {stores:{...n.stores,[t]:a}}});},resetStore:t=>{r(e=>{let o=e.stores[t];return o?{stores:{...e.stores,[t]:{...o,data:{...o.initialState}}}}:e});},resetAllStores:()=>{r(t=>{let e={};for(let[o,n]of Object.entries(t.stores))e[o]={...n,data:{...n.initialState}};return {stores:e}});},resetNonPersistentStores:()=>{r(t=>{let e={};for(let[o,n]of Object.entries(t.stores))e[o]=n.config.persistOnNavigation===true?n:{...n,data:{...n.initialState}};return {stores:e}});}}),{name:"DynamicStoresManager"}));function c(r,t){let e=i(),o=e.stores[r];return useEffect(()=>{!o&&t?.initialState!==void 0&&e.setStoreData(r,{},t);},[r]),{data:o?.data??t?.initialState??{},setData:a=>{if(typeof a=="function"){let y=e.stores[r]?.data??t?.initialState??{},D=a(y);e.setStoreData(r,D,t);}else e.setStoreData(r,a,t);},reset:()=>{e.resetStore(r);}}}function g(r,t){let{data:e,setData:o,reset:n}=c(r,t);return useEffect(()=>()=>{t?.resetOnUnmount===true&&n();},[r,t?.resetOnUnmount]),{data:e,setData:o,reset:n}}var l=(r,t)=>{i.getState().setStoreData(r,t);},x=r=>{i.getState().resetStore(r);},T=()=>{i.getState().resetAllStores();},A=()=>{i.getState().resetNonPersistentStores();};export{d as createDynamicStore,T as resetAllDynamicStores,x as resetDynamicStore,A as resetNonPersistentDynamicStores,l as updateDynamicStore,c as useDynamicStore,g as useDynamicStoreWithCleanup};//# sourceMappingURL=index.mjs.map
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createDynamicStore.ts","../src/dynamicStore.ts"],"names":["createDynamicStore","config","initialState","actions","store","create","set","get","useDynamicStoresManager","devtools","storeId","data","state","existingStore","entry","next","id","useDynamicStore","storesManager","storeRegistry","useEffect","updater","currentData","updates","useDynamicStoreWithCleanup","setData","reset","updateDynamicStore","resetDynamicStore","resetAllDynamicStores","resetNonPersistentDynamicStores"],"mappings":"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","file":"index.mjs","sourcesContent":["import { create } from \"zustand\";\nimport type {\n DynamicStore,\n DynamicStoreConfig,\n StoreActions,\n StoreState,\n StoreSlice,\n} from \"./types\";\n\n/**\n * Creates a dynamic Zustand store from a configuration object.\n *\n * @example\n * ```ts\n * const { useStore } = createDynamicStore({\n * initialState: { count: 0 },\n * actions: (set) => ({\n * increment: () => set((s) => ({ count: s.count + 1 })),\n * decrement: () => set((s) => ({ count: s.count - 1 })),\n * reset: () => set({ count: 0 }),\n * }),\n * });\n * ```\n */\nexport function createDynamicStore<\n TState extends StoreState,\n TActions extends StoreActions,\n>(\n config: DynamicStoreConfig<TState, TActions>,\n): DynamicStore<TState, TActions> {\n const { initialState, actions } = config;\n\n const store = create<StoreSlice<TState, TActions>>()((set, get) => ({\n ...initialState,\n ...actions(set, get),\n }));\n\n return {\n useStore: store,\n store,\n };\n}\n","import { create } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport { useEffect } from \"react\";\nimport type {\n SetStateAction,\n StoreConfig,\n DynamicStoreRegistry,\n StoreState,\n} from \"./types\";\n\n// ─── Internal manager state ───────────────────────────────────────────────────\n\ninterface DynamicStoresState {\n stores: Record<string, DynamicStoreRegistry>;\n setStoreData: (\n storeId: string,\n data: StoreState,\n config?: StoreConfig,\n ) => void;\n resetStore: (storeId: string) => void;\n resetAllStores: () => void;\n resetNonPersistentStores: () => void;\n}\n\nconst useDynamicStoresManager = create<DynamicStoresState>()(\n devtools(\n (set) => ({\n stores: {},\n\n setStoreData: (storeId, data, config) => {\n set((state) => {\n const existingStore = state.stores[storeId];\n const currentData: StoreState =\n existingStore?.data ?? config?.initialState ?? {};\n\n const entry: DynamicStoreRegistry = {\n data: { ...currentData, ...data },\n config: config ?? existingStore?.config ?? {},\n initialState:\n config?.initialState ?? existingStore?.initialState ?? {},\n };\n\n return {\n stores: { ...state.stores, [storeId]: entry },\n };\n });\n },\n\n resetStore: (storeId) => {\n set((state) => {\n const store = state.stores[storeId];\n if (!store) return state;\n\n return {\n stores: {\n ...state.stores,\n [storeId]: { ...store, data: { ...store.initialState } },\n },\n };\n });\n },\n\n resetAllStores: () => {\n set((state) => {\n const next: Record<string, DynamicStoreRegistry> = {};\n\n for (const [id, store] of Object.entries(state.stores)) {\n next[id] = { ...store, data: { ...store.initialState } };\n }\n\n return { stores: next };\n });\n },\n\n resetNonPersistentStores: () => {\n set((state) => {\n const next: Record<string, DynamicStoreRegistry> = {};\n\n for (const [id, store] of Object.entries(state.stores)) {\n next[id] =\n store.config.persistOnNavigation === true\n ? store\n : { ...store, data: { ...store.initialState } };\n }\n\n return { stores: next };\n });\n },\n }),\n { name: \"DynamicStoresManager\" },\n ),\n);\n\n// ─── Return types ─────────────────────────────────────────────────────────────\n\nexport interface UseDynamicStoreReturn<T extends StoreState> {\n data: T;\n setData: (updater: SetStateAction<T>) => void;\n reset: () => void;\n}\n\n// ─── useDynamicStore ──────────────────────────────────────────────────────────\n\n/**\n * Hook-based dynamic store keyed by `storeId`.\n *\n * `setData` accepts either a partial object **or** a function that receives\n * the previous state and returns a partial object — exactly like React's\n * `useState` setter.\n *\n * @example\n * ```tsx\n * const { data, setData, reset } = useDynamicStore<CounterState>('counter', {\n * initialState: { value: 0, step: 1 },\n * });\n *\n * // object update\n * setData({ value: 42 });\n *\n * // functional update — safe for rapid successive calls\n * setData((prev) => ({ value: prev.value + prev.step }));\n * ```\n */\nexport function useDynamicStore<T extends StoreState>(\n storeId: string,\n config?: StoreConfig<T>,\n): UseDynamicStoreReturn<T> {\n const storesManager = useDynamicStoresManager();\n const storeRegistry = storesManager.stores[storeId];\n\n // Initialize the store entry on first use\n useEffect(() => {\n if (!storeRegistry && config?.initialState !== undefined) {\n storesManager.setStoreData(storeId, {}, config as StoreConfig);\n }\n // Only run on mount / storeId change — intentional dep list\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storeId]);\n\n const data = (storeRegistry?.data ?? config?.initialState ?? {}) as T;\n\n const setData = (updater: SetStateAction<T>): void => {\n if (typeof updater === \"function\") {\n const currentData = (\n storesManager.stores[storeId]?.data ?? config?.initialState ?? {}\n ) as T;\n const updates = updater(currentData);\n storesManager.setStoreData(\n storeId,\n updates as StoreState,\n config as StoreConfig | undefined,\n );\n } else {\n storesManager.setStoreData(\n storeId,\n updater as StoreState,\n config as StoreConfig | undefined,\n );\n }\n };\n\n const reset = (): void => {\n storesManager.resetStore(storeId);\n };\n\n return { data, setData, reset };\n}\n\n// ─── useDynamicStoreWithCleanup ───────────────────────────────────────────────\n\n/**\n * Same as `useDynamicStore` but automatically resets state when the\n * component unmounts (when `config.resetOnUnmount` is `true`).\n *\n * @example\n * ```tsx\n * const { data, setData, reset } = useDynamicStoreWithCleanup<FormState>(\n * 'editForm',\n * { initialState, resetOnUnmount: true },\n * );\n * ```\n */\nexport function useDynamicStoreWithCleanup<T extends StoreState>(\n storeId: string,\n config?: StoreConfig<T>,\n): UseDynamicStoreReturn<T> {\n const { data, setData, reset } = useDynamicStore<T>(storeId, config);\n\n useEffect(() => {\n return () => {\n if (config?.resetOnUnmount === true) {\n reset();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [storeId, config?.resetOnUnmount]);\n\n return { data, setData, reset };\n}\n\n// ─── Imperative helpers (outside React) ──────────────────────────────────────\n\n/**\n * Update a dynamic store from outside a React component.\n */\nexport const updateDynamicStore = (\n storeId: string,\n data: StoreState,\n): void => {\n useDynamicStoresManager.getState().setStoreData(storeId, data);\n};\n\n/**\n * Reset a single dynamic store to its initial state from outside React.\n */\nexport const resetDynamicStore = (storeId: string): void => {\n useDynamicStoresManager.getState().resetStore(storeId);\n};\n\n/**\n * Reset all dynamic stores to their initial states from outside React.\n */\nexport const resetAllDynamicStores = (): void => {\n useDynamicStoresManager.getState().resetAllStores();\n};\n\n/**\n * Reset only stores that do not have `persistOnNavigation: true`.\n */\nexport const resetNonPersistentDynamicStores = (): void => {\n useDynamicStoresManager.getState().resetNonPersistentStores();\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pitboxdev/dynamic-store-zustand",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Dynamic store factory built on top of Zustand for scalable state management",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"type-check": "tsc --noEmit",
|
|
22
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"release": "npm version patch && git push origin main --follow-tags && npm publish"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"zustand",
|
|
29
|
+
"pitboxdev",
|
|
30
|
+
"state-management",
|
|
31
|
+
"react",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"author": "Pitboxdev",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"react": ">=18.0.0",
|
|
38
|
+
"zustand": ">=5.0.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react": "^18.3.12",
|
|
42
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
43
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
44
|
+
"eslint": "^9.0.0",
|
|
45
|
+
"react": "^18.3.1",
|
|
46
|
+
"tsup": "^8.3.5",
|
|
47
|
+
"typescript": "^5.7.2",
|
|
48
|
+
"vitest": "^2.1.8",
|
|
49
|
+
"zustand": "^5.0.2"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/pitboxdev/dynamic-store-zustand.git"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/pitboxdev/dynamic-store-zustand/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/pitboxdev/dynamic-store-zustand#readme",
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|