cogsbox-state 0.5.7 → 0.5.9
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 +521 -279
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,386 +1,628 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Cogsbox State: A Practical Guide
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **⚠️ WARNING**: This README is AI-generated based on the current implementation of Cogsbox State. The library is under active development and APIs are subject to change. Always refer to the official documentation or source code for the most up-to-date information.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Getting Started
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Cogsbox State is a React state management library that provides a fluent interface for managing complex state.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
### Basic Setup
|
|
10
10
|
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- 🔄 **Fluent Array Operations**
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// Find, filter, and update in one chain
|
|
28
|
-
updater.items.findWith("id", "123").status.update("complete");
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
- 📝 **Smart Form Handling**
|
|
32
|
-
|
|
33
|
-
- Automatic debouncing
|
|
34
|
-
- Validation integration
|
|
35
|
-
- Form state synchronization
|
|
36
|
-
|
|
37
|
-
- 🔍 **Powerful Array Methods**
|
|
11
|
+
```typescript
|
|
12
|
+
// 1. Define your initial state
|
|
13
|
+
const InitialState = {
|
|
14
|
+
users: [],
|
|
15
|
+
settings: {
|
|
16
|
+
darkMode: false,
|
|
17
|
+
notifications: true
|
|
18
|
+
},
|
|
19
|
+
cart: {
|
|
20
|
+
items: [],
|
|
21
|
+
total: 0
|
|
22
|
+
}
|
|
23
|
+
};
|
|
38
24
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- Unique insertions
|
|
42
|
-
- Map with updaters
|
|
25
|
+
// 2. Create the state hook
|
|
26
|
+
export const { useCogsState } = createCogsState(InitialState);
|
|
43
27
|
|
|
44
|
-
|
|
28
|
+
// 3. Use in your component
|
|
29
|
+
function MyComponent() {
|
|
30
|
+
const cart = useCogsState("cart");
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
32
|
+
// Access values
|
|
33
|
+
const cartItems = cart.items.get();
|
|
34
|
+
const total = cart.total.get();
|
|
49
35
|
|
|
50
|
-
|
|
36
|
+
// Update values
|
|
37
|
+
const addItem = (item) => {
|
|
38
|
+
cart.items.insert(item);
|
|
39
|
+
cart.total.update(total + item.price);
|
|
40
|
+
};
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
42
|
+
return (
|
|
43
|
+
// Your component JSX
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
- Granular updates
|
|
58
|
-
- Automatic optimization
|
|
59
|
-
- Path-based subscriptions
|
|
48
|
+
## Core Concepts
|
|
60
49
|
|
|
61
|
-
|
|
50
|
+
### Accessing State
|
|
62
51
|
|
|
63
52
|
```typescript
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
id: string;
|
|
67
|
-
name: string;
|
|
68
|
-
price: number;
|
|
69
|
-
categories: string[];
|
|
70
|
-
variants: {
|
|
71
|
-
id: string;
|
|
72
|
-
color: string;
|
|
73
|
-
size: string;
|
|
74
|
-
stock: number;
|
|
75
|
-
}[];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
interface CartItem {
|
|
79
|
-
productId: string;
|
|
80
|
-
variantId: string;
|
|
81
|
-
quantity: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const InitialState = {
|
|
85
|
-
catalog: {
|
|
86
|
-
products: [] as Product[],
|
|
87
|
-
categories: [] as string[],
|
|
88
|
-
filters: {
|
|
89
|
-
priceRange: { min: 0, max: 100 },
|
|
90
|
-
selectedCategories: [] as string[],
|
|
91
|
-
search: "",
|
|
92
|
-
},
|
|
93
|
-
sortBy: "price_asc" as "price_asc" | "price_desc" | "name",
|
|
94
|
-
},
|
|
95
|
-
cart: {
|
|
96
|
-
items: [] as CartItem[],
|
|
97
|
-
couponCode: "",
|
|
98
|
-
shipping: {
|
|
99
|
-
address: "",
|
|
100
|
-
method: "standard" as "standard" | "express",
|
|
101
|
-
cost: 0,
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
ui: {
|
|
105
|
-
sidebarOpen: false,
|
|
106
|
-
activeProductId: null as string | null,
|
|
107
|
-
notifications: [] as {
|
|
108
|
-
id: string;
|
|
109
|
-
message: string;
|
|
110
|
-
type: "success" | "error";
|
|
111
|
-
}[],
|
|
112
|
-
},
|
|
113
|
-
};
|
|
53
|
+
// Get the entire state object
|
|
54
|
+
const entireCart = cart.get();
|
|
114
55
|
|
|
115
|
-
//
|
|
116
|
-
|
|
56
|
+
// Access a specific property
|
|
57
|
+
const cartItems = cart.items.get();
|
|
117
58
|
|
|
118
|
-
//
|
|
119
|
-
const
|
|
59
|
+
// Access nested properties
|
|
60
|
+
const firstItemPrice = cart.items.index(0).price.get();
|
|
120
61
|
```
|
|
121
62
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
### Basic Updates
|
|
125
|
-
|
|
126
|
-
#### `.update()`
|
|
127
|
-
|
|
128
|
-
Updates state value directly.
|
|
63
|
+
### Updating State
|
|
129
64
|
|
|
130
65
|
```typescript
|
|
131
66
|
// Direct update
|
|
132
|
-
|
|
67
|
+
cart.settings.darkMode.update(true);
|
|
133
68
|
|
|
134
|
-
// Functional update
|
|
135
|
-
|
|
69
|
+
// Functional update (based on previous value)
|
|
70
|
+
cart.cart.total.update((prev) => prev + 10);
|
|
136
71
|
|
|
137
72
|
// Deep update
|
|
138
|
-
|
|
139
|
-
.findWith("id", "123")
|
|
140
|
-
.variants[0].stock.update((prev) => prev - 1);
|
|
73
|
+
cart.users.findWith("id", "123").name.update("New Name");
|
|
141
74
|
```
|
|
142
75
|
|
|
143
|
-
|
|
76
|
+
## Working with Arrays
|
|
144
77
|
|
|
145
|
-
|
|
78
|
+
### Basic Array Operations
|
|
146
79
|
|
|
147
80
|
```typescript
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
81
|
+
// Add an item
|
|
82
|
+
cart.cart.items.insert({ id: "prod1", name: "Product 1", price: 29.99 });
|
|
83
|
+
|
|
84
|
+
// Remove an item at index
|
|
85
|
+
cart.cart.items.cut(2);
|
|
153
86
|
|
|
154
|
-
|
|
87
|
+
// Find and update an item
|
|
88
|
+
cart.cart.items.findWith("id", "prod1").quantity.update((prev) => prev + 1);
|
|
155
89
|
|
|
156
|
-
|
|
90
|
+
// Update item at specific index
|
|
91
|
+
cart.cart.items.index(0).price.update(19.99);
|
|
92
|
+
```
|
|
157
93
|
|
|
158
|
-
|
|
94
|
+
### Advanced Array Methods
|
|
159
95
|
|
|
160
96
|
```typescript
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
97
|
+
// Map with access to updaters
|
|
98
|
+
cart.cart.items.stateMap((item, itemUpdater) => (
|
|
99
|
+
<CartItem
|
|
100
|
+
key={item.id}
|
|
101
|
+
item={item}
|
|
102
|
+
onQuantityChange={qty => itemUpdater.quantity.update(qty)}
|
|
103
|
+
/>
|
|
104
|
+
));
|
|
105
|
+
|
|
106
|
+
// Filter items while maintaining updater capabilities
|
|
107
|
+
const inStockItems = cart.products.stateFilter(product => product.stock > 0);
|
|
108
|
+
|
|
109
|
+
// Insert only if the item doesn't exist
|
|
110
|
+
cart.cart.items.uniqueInsert(
|
|
111
|
+
{ id: "prod1", quantity: 1 },
|
|
112
|
+
["id"] // fields to check for uniqueness
|
|
113
|
+
);
|
|
169
114
|
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
productId: "123",
|
|
173
|
-
variantId: "v1",
|
|
174
|
-
quantity: 1,
|
|
175
|
-
}));
|
|
115
|
+
// Flatten nested arrays by property
|
|
116
|
+
const allVariants = cart.products.stateFlattenOn("variants");
|
|
176
117
|
```
|
|
177
118
|
|
|
178
|
-
|
|
119
|
+
## Reactivity Control
|
|
120
|
+
|
|
121
|
+
Cogsbox offers different ways to control when components re-render:
|
|
179
122
|
|
|
180
|
-
|
|
123
|
+
### Component Reactivity (Default)
|
|
124
|
+
|
|
125
|
+
Re-renders when any accessed value changes.
|
|
181
126
|
|
|
182
127
|
```typescript
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
{
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
type: "success",
|
|
192
|
-
},
|
|
193
|
-
["id"],
|
|
128
|
+
// Default behavior - re-renders when cart.items or cart.total changes
|
|
129
|
+
const cart = useCogsState("cart");
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div>
|
|
133
|
+
<div>Items: {cart.items.get().length}</div>
|
|
134
|
+
<div>Total: {cart.total.get()}</div>
|
|
135
|
+
</div>
|
|
194
136
|
);
|
|
195
137
|
```
|
|
196
138
|
|
|
197
|
-
|
|
139
|
+
### Dependency-Based Reactivity
|
|
198
140
|
|
|
199
|
-
|
|
141
|
+
Re-renders only when specified dependencies change.
|
|
200
142
|
|
|
201
143
|
```typescript
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
144
|
+
// Only re-renders when items array or status changes
|
|
145
|
+
const cart = useCogsState("cart", {
|
|
146
|
+
reactiveType: ["deps"],
|
|
147
|
+
reactiveDeps: (state) => [state.items, state.status],
|
|
148
|
+
});
|
|
207
149
|
```
|
|
208
150
|
|
|
209
|
-
|
|
151
|
+
### Full Reactivity
|
|
210
152
|
|
|
211
|
-
|
|
153
|
+
Re-renders on any state change, even for unused properties.
|
|
212
154
|
|
|
213
155
|
```typescript
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
updater.cart.items
|
|
219
|
-
.findWith("productId", 123)
|
|
220
|
-
.quantity.update((prev) => prev + 1);
|
|
156
|
+
// Re-renders on any change to cart state
|
|
157
|
+
const cart = useCogsState("cart", {
|
|
158
|
+
reactiveType: ["all"],
|
|
159
|
+
});
|
|
221
160
|
```
|
|
222
161
|
|
|
223
|
-
|
|
162
|
+
### Signal-Based Reactivity
|
|
224
163
|
|
|
225
|
-
|
|
164
|
+
Updates only the DOM elements that depend on changed values.
|
|
226
165
|
|
|
227
166
|
```typescript
|
|
228
|
-
//
|
|
229
|
-
|
|
167
|
+
// Most efficient - updates just the specific DOM elements
|
|
168
|
+
return (
|
|
169
|
+
<div>
|
|
170
|
+
<div>Items: {cart.items.$derive(items => items.length)}</div>
|
|
171
|
+
<div>Total: {cart.total.$get()}</div>
|
|
172
|
+
</div>
|
|
173
|
+
);
|
|
230
174
|
```
|
|
231
175
|
|
|
232
|
-
|
|
176
|
+
## Form Integration
|
|
177
|
+
|
|
178
|
+
Cogsbox State provides an intuitive form system to connect your state to form controls with built-in validation, error handling, and array support.
|
|
233
179
|
|
|
234
|
-
|
|
180
|
+
### Basic Form Element Usage
|
|
181
|
+
|
|
182
|
+
The `formElement` method serves as the bridge between state and UI:
|
|
235
183
|
|
|
236
184
|
```typescript
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
185
|
+
// Direct value/onChange pattern for complete control
|
|
186
|
+
user.firstName.formElement((params) => (
|
|
187
|
+
<div>
|
|
188
|
+
<label className="block text-sm font-medium">First Name</label>
|
|
189
|
+
<input
|
|
190
|
+
type="text"
|
|
191
|
+
className="mt-1 block w-full rounded-md border-2 p-2"
|
|
192
|
+
value={params.get()}
|
|
193
|
+
onChange={(e) => params.set(e.target.value)}
|
|
194
|
+
onBlur={params.inputProps.onBlur}
|
|
195
|
+
ref={params.inputProps.ref}
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
));
|
|
199
|
+
|
|
200
|
+
// Using inputProps shorthand for simpler binding
|
|
201
|
+
user.lastName.formElement((params) => (
|
|
202
|
+
<div>
|
|
203
|
+
<label className="block text-sm font-medium">Last Name</label>
|
|
204
|
+
<input
|
|
205
|
+
type="text"
|
|
206
|
+
className="mt-1 block w-full rounded-md border-2 p-2"
|
|
207
|
+
{...params.inputProps}
|
|
208
|
+
/>
|
|
209
|
+
</div>
|
|
210
|
+
));
|
|
241
211
|
```
|
|
242
212
|
|
|
243
|
-
|
|
213
|
+
### Form Validation Options
|
|
244
214
|
|
|
245
|
-
|
|
215
|
+
Cogsbox provides several approaches to validation:
|
|
246
216
|
|
|
247
217
|
```typescript
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
218
|
+
// Custom validation message
|
|
219
|
+
user.email.formElement(
|
|
220
|
+
(params) => (
|
|
221
|
+
<div>
|
|
222
|
+
<label>Email Address</label>
|
|
223
|
+
<input {...params.inputProps} type="email" />
|
|
224
|
+
</div>
|
|
225
|
+
),
|
|
226
|
+
{
|
|
227
|
+
validation: {
|
|
228
|
+
message: "Please enter a valid email address"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
251
231
|
);
|
|
252
232
|
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
233
|
+
// Hidden validation (show border but no message)
|
|
234
|
+
user.lastName.formElement(
|
|
235
|
+
(params) => (
|
|
236
|
+
<div>
|
|
237
|
+
<label>Last Name</label>
|
|
238
|
+
<input
|
|
239
|
+
{...params.inputProps}
|
|
240
|
+
className={`input ${params.validationErrors().length > 0 ? 'border-red-500' : ''}`}
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
),
|
|
244
|
+
{
|
|
245
|
+
validation: {
|
|
246
|
+
hideMessage: true
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Custom validation with onBlur
|
|
252
|
+
user.phone.formElement((params) => (
|
|
253
|
+
<div>
|
|
254
|
+
<label>Phone Number</label>
|
|
255
|
+
<input
|
|
256
|
+
{...params.inputProps}
|
|
257
|
+
onBlur={(e) => {
|
|
258
|
+
if (e.target.value.length == 0 || isNaN(Number(e.target.value))) {
|
|
259
|
+
params.addValidationError("Please enter a valid phone number");
|
|
260
|
+
}
|
|
261
|
+
}}
|
|
262
|
+
placeholder="(555) 123-4567"
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
));
|
|
257
266
|
```
|
|
258
267
|
|
|
259
|
-
|
|
268
|
+
### Working with Form Arrays
|
|
260
269
|
|
|
261
|
-
|
|
270
|
+
For managing collections like addresses:
|
|
262
271
|
|
|
263
272
|
```typescript
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
273
|
+
function AddressesManager() {
|
|
274
|
+
const [currentAddressIndex, setCurrentAddressIndex] = useState(0);
|
|
275
|
+
const user = useCogsState("user");
|
|
276
|
+
|
|
277
|
+
// Add new address
|
|
278
|
+
const addNewAddress = () => {
|
|
279
|
+
user.addresses.insert({
|
|
280
|
+
street: "",
|
|
281
|
+
city: "",
|
|
282
|
+
state: "",
|
|
283
|
+
zipCode: "",
|
|
284
|
+
country: "USA",
|
|
285
|
+
isDefault: false,
|
|
286
|
+
});
|
|
287
|
+
setCurrentAddressIndex(user.addresses.get().length - 1);
|
|
288
|
+
};
|
|
267
289
|
|
|
268
|
-
|
|
290
|
+
return (
|
|
291
|
+
<div>
|
|
292
|
+
{/* Address tabs with validation indicators */}
|
|
293
|
+
<div className="flex space-x-2 mt-2">
|
|
294
|
+
{user.addresses.stateMap((_, setter, index) => {
|
|
295
|
+
const errorCount = setter.showValidationErrors().length;
|
|
296
|
+
return (
|
|
297
|
+
<button
|
|
298
|
+
key={index}
|
|
299
|
+
onClick={() => setCurrentAddressIndex(index)}
|
|
300
|
+
className={`rounded-lg flex items-center justify-center ${
|
|
301
|
+
errorCount > 0
|
|
302
|
+
? "border-red-500 bg-red-400"
|
|
303
|
+
: currentAddressIndex === index
|
|
304
|
+
? "bg-blue-500 text-white"
|
|
305
|
+
: "bg-gray-200"
|
|
306
|
+
}`}
|
|
307
|
+
>
|
|
308
|
+
{index + 1}
|
|
309
|
+
{errorCount > 0 && (
|
|
310
|
+
<div className="bg-red-500 text-white rounded-full">
|
|
311
|
+
{errorCount}
|
|
312
|
+
</div>
|
|
313
|
+
)}
|
|
314
|
+
</button>
|
|
315
|
+
);
|
|
316
|
+
})}
|
|
317
|
+
<button onClick={addNewAddress}>Add</button>
|
|
318
|
+
</div>
|
|
319
|
+
|
|
320
|
+
{/* Current address form */}
|
|
321
|
+
{user.addresses.get().length > 0 && (
|
|
322
|
+
<div className="grid grid-cols-1 gap-4">
|
|
323
|
+
{/* Access fields with index() method */}
|
|
324
|
+
{user.addresses.index(currentAddressIndex).street.formElement(
|
|
325
|
+
(params) => (
|
|
326
|
+
<div>
|
|
327
|
+
<label>Street</label>
|
|
328
|
+
<input value={params.get()} onChange={(e) => params.set(e.target.value)} />
|
|
329
|
+
</div>
|
|
330
|
+
),
|
|
331
|
+
{
|
|
332
|
+
validation: {
|
|
333
|
+
message: "Street address is required"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{/* City and State in a row */}
|
|
339
|
+
<div className="grid grid-cols-2 gap-4">
|
|
340
|
+
{user.addresses.index(currentAddressIndex).city.formElement((params) => (
|
|
341
|
+
<div>
|
|
342
|
+
<label>City</label>
|
|
343
|
+
<input {...params.inputProps} />
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
|
|
347
|
+
{user.addresses.index(currentAddressIndex).state.formElement((params) => (
|
|
348
|
+
<div>
|
|
349
|
+
<label>State</label>
|
|
350
|
+
<input {...params.inputProps} />
|
|
351
|
+
</div>
|
|
352
|
+
))}
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
{/* Boolean field handling */}
|
|
356
|
+
{user.addresses.index(currentAddressIndex).isDefault.formElement((params) => (
|
|
357
|
+
<div className="flex items-center">
|
|
358
|
+
<input
|
|
359
|
+
type="checkbox"
|
|
360
|
+
checked={params.get()}
|
|
361
|
+
onChange={(e) => params.set(e.target.checked)}
|
|
362
|
+
id={`default-address-${currentAddressIndex}`}
|
|
363
|
+
/>
|
|
364
|
+
<label htmlFor={`default-address-${currentAddressIndex}`}>
|
|
365
|
+
Set as default address
|
|
366
|
+
</label>
|
|
367
|
+
</div>
|
|
368
|
+
))}
|
|
369
|
+
|
|
370
|
+
{/* Remove address button */}
|
|
371
|
+
{user.addresses.get().length > 1 && (
|
|
372
|
+
<button
|
|
373
|
+
onClick={() => {
|
|
374
|
+
user.addresses.cut(currentAddressIndex);
|
|
375
|
+
setCurrentAddressIndex(Math.max(0, currentAddressIndex - 1));
|
|
376
|
+
}}
|
|
377
|
+
>
|
|
378
|
+
Remove Selected Address
|
|
379
|
+
</button>
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
```
|
|
269
387
|
|
|
270
|
-
|
|
388
|
+
### Form Actions
|
|
271
389
|
|
|
272
|
-
|
|
390
|
+
Cogsbox provides methods to manage form state:
|
|
273
391
|
|
|
274
392
|
```typescript
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
393
|
+
// Reset form to initial state
|
|
394
|
+
const handleReset = () => {
|
|
395
|
+
user.revertToInitialState();
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Validate all fields using Zod schema
|
|
399
|
+
const handleSubmit = () => {
|
|
400
|
+
if (user.validateZodSchema()) {
|
|
401
|
+
// All valid, proceed with submission
|
|
402
|
+
submitData(user.get());
|
|
403
|
+
}
|
|
404
|
+
};
|
|
284
405
|
```
|
|
285
406
|
|
|
286
|
-
###
|
|
407
|
+
### Setting Up Zod Validation
|
|
287
408
|
|
|
288
|
-
|
|
409
|
+
```typescript
|
|
410
|
+
// Setting up validation at initialization
|
|
411
|
+
export const { useCogsState } = createCogsState({
|
|
412
|
+
user: {
|
|
413
|
+
initialState: {
|
|
414
|
+
firstName: "",
|
|
415
|
+
lastName: "",
|
|
416
|
+
email: "",
|
|
417
|
+
phone: "",
|
|
418
|
+
addresses: [
|
|
419
|
+
{
|
|
420
|
+
street: "",
|
|
421
|
+
city: "",
|
|
422
|
+
state: "",
|
|
423
|
+
zipCode: "",
|
|
424
|
+
country: "USA",
|
|
425
|
+
isDefault: false,
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
},
|
|
429
|
+
validation: {
|
|
430
|
+
key: "userForm", // Used for error tracking
|
|
431
|
+
zodSchema: z.object({
|
|
432
|
+
firstName: z.string().min(1, "First name is required"),
|
|
433
|
+
lastName: z.string().min(1, "Last name is required"),
|
|
434
|
+
email: z.string().email("Please enter a valid email"),
|
|
435
|
+
phone: z.string().min(10, "Phone number must be at least 10 digits"),
|
|
436
|
+
addresses: z.array(
|
|
437
|
+
z.object({
|
|
438
|
+
street: z.string().min(1, "Street is required"),
|
|
439
|
+
city: z.string().min(1, "City is required"),
|
|
440
|
+
state: z.string().min(1, "State is required"),
|
|
441
|
+
zipCode: z
|
|
442
|
+
.string()
|
|
443
|
+
.min(5, "Zip code must be at least 5 characters"),
|
|
444
|
+
country: z.string(),
|
|
445
|
+
isDefault: z.boolean(),
|
|
446
|
+
})
|
|
447
|
+
),
|
|
448
|
+
}),
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
```
|
|
289
453
|
|
|
290
|
-
|
|
454
|
+
## Server Synchronization
|
|
291
455
|
|
|
292
456
|
```typescript
|
|
293
|
-
//
|
|
294
|
-
|
|
457
|
+
// Setting up server sync
|
|
458
|
+
const products = useCogsState("products", {
|
|
459
|
+
serverSync: {
|
|
460
|
+
syncKey: "products",
|
|
461
|
+
syncFunction: ({ state }) => api.updateProducts(state),
|
|
462
|
+
debounce: 1000, // ms
|
|
463
|
+
mutation: useMutation(api.updateProducts),
|
|
464
|
+
},
|
|
465
|
+
});
|
|
295
466
|
|
|
296
|
-
//
|
|
297
|
-
|
|
467
|
+
// State is automatically synced with server after changes
|
|
468
|
+
products.items.index(0).stock.update((prev) => prev - 1);
|
|
298
469
|
```
|
|
299
470
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
Gets currently selected item from a list.
|
|
471
|
+
## Local Storage Persistence
|
|
303
472
|
|
|
304
473
|
```typescript
|
|
305
|
-
|
|
474
|
+
// Automatically save state to localStorage
|
|
475
|
+
const cart = useCogsState("cart", {
|
|
476
|
+
localStorage: {
|
|
477
|
+
key: "shopping-cart",
|
|
478
|
+
},
|
|
479
|
+
});
|
|
306
480
|
```
|
|
307
481
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
## Examples
|
|
311
|
-
|
|
312
|
-
### Product Catalog Management
|
|
482
|
+
## Example: Shopping Cart
|
|
313
483
|
|
|
314
484
|
```typescript
|
|
315
|
-
function
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
485
|
+
function ShoppingCart() {
|
|
486
|
+
const cart = useCogsState("cart");
|
|
487
|
+
const products = useCogsState("products");
|
|
488
|
+
|
|
489
|
+
const addToCart = (productId) => {
|
|
490
|
+
const product = products.items.findWith("id", productId).get();
|
|
491
|
+
|
|
492
|
+
cart.items.uniqueInsert(
|
|
493
|
+
{
|
|
494
|
+
productId,
|
|
495
|
+
name: product.name,
|
|
496
|
+
price: product.price,
|
|
497
|
+
quantity: 1
|
|
498
|
+
},
|
|
499
|
+
["productId"],
|
|
500
|
+
// If product exists, update quantity instead
|
|
501
|
+
(existingItem) => ({
|
|
502
|
+
...existingItem,
|
|
503
|
+
quantity: existingItem.quantity + 1
|
|
504
|
+
})
|
|
331
505
|
);
|
|
332
506
|
|
|
507
|
+
// Update total
|
|
508
|
+
cart.total.update(prev => prev + product.price);
|
|
509
|
+
};
|
|
510
|
+
|
|
333
511
|
return (
|
|
334
512
|
<div>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
.update(
|
|
345
|
-
|
|
346
|
-
|
|
513
|
+
<h2>Your Cart</h2>
|
|
514
|
+
|
|
515
|
+
{cart.items.stateMap((item, itemUpdater) => (
|
|
516
|
+
<div key={item.productId} className="cart-item">
|
|
517
|
+
<div>{item.name}</div>
|
|
518
|
+
<div>${item.price}</div>
|
|
519
|
+
|
|
520
|
+
<div className="quantity">
|
|
521
|
+
<button onClick={() =>
|
|
522
|
+
itemUpdater.quantity.update(prev => Math.max(prev - 1, 0))
|
|
523
|
+
}>-</button>
|
|
524
|
+
|
|
525
|
+
<span>{item.quantity}</span>
|
|
526
|
+
|
|
527
|
+
<button onClick={() =>
|
|
528
|
+
itemUpdater.quantity.update(prev => prev + 1)
|
|
529
|
+
}>+</button>
|
|
530
|
+
</div>
|
|
531
|
+
|
|
532
|
+
<button onClick={() => itemUpdater.cut()}>Remove</button>
|
|
533
|
+
</div>
|
|
347
534
|
))}
|
|
535
|
+
|
|
536
|
+
<div className="cart-total">
|
|
537
|
+
<strong>Total:</strong> ${cart.total.get()}
|
|
538
|
+
</div>
|
|
348
539
|
</div>
|
|
349
540
|
);
|
|
350
541
|
}
|
|
351
542
|
```
|
|
352
543
|
|
|
353
|
-
|
|
544
|
+
## Session Support
|
|
354
545
|
|
|
355
|
-
|
|
356
|
-
function CartManager() {
|
|
357
|
-
const [state, updater] = useCogsState("cart", {
|
|
358
|
-
localStorage: {
|
|
359
|
-
key: "shopping-cart"
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const addToCart = (product: Product, variantId: string) => {
|
|
364
|
-
updater.items.uniqueInsert({
|
|
365
|
-
productId: product.id,
|
|
366
|
-
variantId,
|
|
367
|
-
quantity: 1
|
|
368
|
-
}, ['productId', 'variantId']);
|
|
369
|
-
};
|
|
546
|
+
Cogsbox State supports session-based state management through the `useCogsConfig` hook, allowing you to isolate state for different user sessions:
|
|
370
547
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
);
|
|
383
|
-
}
|
|
548
|
+
```typescript
|
|
549
|
+
// Using session-specific state
|
|
550
|
+
const cart = useCogsState("cart", {
|
|
551
|
+
localStorageKey: "user-cart", // Will be prefixed with sessionId
|
|
552
|
+
initState: {
|
|
553
|
+
initialState: {
|
|
554
|
+
items: [],
|
|
555
|
+
total: 0,
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
});
|
|
384
559
|
```
|
|
385
560
|
|
|
386
|
-
|
|
561
|
+
## Performance Optimizations
|
|
562
|
+
|
|
563
|
+
The library includes several performance optimizations:
|
|
564
|
+
|
|
565
|
+
1. **Cache Management**: Cogsbox maintains a cache of proxy objects to reduce re-creation overhead.
|
|
566
|
+
2. **Batched Updates**: State updates are batched where possible to minimize render cycles.
|
|
567
|
+
3. **Signal-based DOM Updates**: Directly update DOM elements without re-rendering components.
|
|
568
|
+
|
|
569
|
+
## Common Patterns and Tips
|
|
570
|
+
|
|
571
|
+
1. **Path-based Updates**: Always use the fluent API to update nested properties.
|
|
572
|
+
|
|
573
|
+
```typescript
|
|
574
|
+
// Good
|
|
575
|
+
user.users.index(0).address.city.update("New York");
|
|
576
|
+
|
|
577
|
+
// Avoid
|
|
578
|
+
user.update({ ...state, users: [...] });
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
2. **Working with Arrays**: Use the built-in array methods instead of manually updating array state.
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// Good
|
|
585
|
+
user.users.insert(newUser);
|
|
586
|
+
user.users.findWith("id", 123).active.update(true);
|
|
587
|
+
|
|
588
|
+
// Avoid
|
|
589
|
+
user.users.update([...users, newUser]);
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
3. **Optimization**: Use the appropriate reactivity type for your needs.
|
|
593
|
+
|
|
594
|
+
```typescript
|
|
595
|
+
// For lists where only specific items change frequently
|
|
596
|
+
user.items.stateMap((item) => (
|
|
597
|
+
<div>{item.$get()}</div> // Only this item re-renders when changed
|
|
598
|
+
));
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
4. **Form Management**: Use formElement for all form inputs to get automatic validation and debouncing.
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
profile.name.formElement(
|
|
605
|
+
({ inputProps }) => <input {...inputProps} />,
|
|
606
|
+
{ debounceTime: 300 }
|
|
607
|
+
);
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
5. **Middleware Support**: Add middleware for logging, analytics, or custom state processing:
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
const user = useCogsState("user", {
|
|
614
|
+
middleware: ({ update, updateLog }) => {
|
|
615
|
+
// Log all state changes
|
|
616
|
+
console.log("State update:", update);
|
|
617
|
+
|
|
618
|
+
// Trigger analytics
|
|
619
|
+
if (update.path.includes("preferences")) {
|
|
620
|
+
analytics.track("preferences_changed", update.newValue);
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## API Reference
|
|
627
|
+
|
|
628
|
+
For a comprehensive API reference, see the TypeScript interfaces and examples in the source code.
|