cogsbox-state 0.5.431 → 0.5.434
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 +596 -238
- package/dist/CogsState.d.ts +93 -104
- package/dist/CogsState.jsx +1540 -1033
- package/dist/CogsState.jsx.map +1 -1
- package/dist/Functions.d.ts +1 -15
- package/dist/Functions.jsx +40 -187
- package/dist/Functions.jsx.map +1 -1
- package/dist/index.js +18 -19
- package/dist/store.d.ts +94 -92
- package/dist/store.js +230 -295
- package/dist/store.js.map +1 -1
- package/dist/useValidateZodPath.d.ts +1 -1
- package/dist/utility.d.ts +2 -2
- package/dist/utility.js +152 -169
- package/dist/utility.js.map +1 -1
- package/package.json +2 -1
- package/src/CogsState.tsx +2858 -1614
- package/src/Functions.tsx +167 -303
- package/src/store.ts +440 -440
- package/src/utility.ts +76 -95
- package/dist/useValidateZodPath.js +0 -59
- package/dist/useValidateZodPath.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,361 +1,719 @@
|
|
|
1
|
-
# Cogsbox State: A
|
|
1
|
+
# Cogsbox State: A Comprehensive Guide
|
|
2
2
|
|
|
3
3
|
> **🚨 DANGER: DO NOT USE - UNSTABLE & EXPERIMENTAL 🚨**
|
|
4
4
|
>
|
|
5
5
|
> This library is in extremely early development and constantly changing.
|
|
6
|
-
>
|
|
7
|
-
> **DO NOT USE IN ANY PROJECT.**
|
|
6
|
+
>
|
|
7
|
+
> **DO NOT USE IN ANY PROJECT YET - ONLY FOR TESTING AND PROVIDING FEEDBACK.**
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## What is Cogsbox State?
|
|
10
10
|
|
|
11
|
-
Cogsbox State is a React state management library that
|
|
11
|
+
Cogsbox State is a React state management library that creates a **nested state builder** - a type-safe proxy that mimics your initial state structure. Every property in your state becomes a powerful state object with built-in methods for updates, arrays, forms, and more.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
**Key Philosophy**: Instead of complex useState drilling and manual mapping, you directly access nested properties and use built-in methods.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
## Getting Started
|
|
16
|
+
|
|
17
|
+
### Basic Setup
|
|
17
18
|
|
|
18
19
|
```typescript
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
},
|
|
29
|
-
// A more complex slice for a shopping cart
|
|
30
|
-
cart: {
|
|
31
|
-
initialState: {
|
|
32
|
-
items: [], // Array of { id, name, price, quantity }
|
|
33
|
-
total: 0,
|
|
34
|
-
status: "active",
|
|
20
|
+
import { createCogsState } from 'cogsbox-state';
|
|
21
|
+
|
|
22
|
+
// 1. Define your initial state structure
|
|
23
|
+
const initialState = {
|
|
24
|
+
user: {
|
|
25
|
+
name: "John",
|
|
26
|
+
stats: {
|
|
27
|
+
counter: 0,
|
|
28
|
+
lastUpdated: null
|
|
35
29
|
},
|
|
30
|
+
age: 30,
|
|
31
|
+
online:false
|
|
36
32
|
},
|
|
33
|
+
todos: [],
|
|
34
|
+
settings: {
|
|
35
|
+
darkMode: false,
|
|
36
|
+
notifications: true
|
|
37
|
+
}
|
|
37
38
|
};
|
|
38
39
|
|
|
39
|
-
// 2. Create
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
2. **Use the hook in your components.**
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
// 3. Use in your component
|
|
47
|
-
function SettingsPanel() {
|
|
48
|
-
// Access the "settings" state slice
|
|
49
|
-
const settings = useCogsState("settings");
|
|
40
|
+
// 2. Create your state manager
|
|
41
|
+
const { useCogsState } = createCogsState(initialState);
|
|
50
42
|
|
|
51
|
-
//
|
|
52
|
-
|
|
43
|
+
// 3. Use in components - access specific state slices by their keys
|
|
44
|
+
function UserComponent() {
|
|
45
|
+
const user = useCogsState('user'); // Access the 'user' slice
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
return (
|
|
48
|
+
<div>
|
|
49
|
+
<p>Name: {user.name.get()}</p>
|
|
50
|
+
<p>Counter: {user.stats.counter.get()}</p>
|
|
51
|
+
<button onClick={() => user.stats.counter.update(prev => prev + 1)}>
|
|
52
|
+
Increment Counter
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
62
56
|
}
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
</
|
|
70
|
-
|
|
71
|
-
|
|
58
|
+
function TodoComponent() {
|
|
59
|
+
const todos = useCogsState('todos'); // Access the 'todos' slice
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
<p>Todo count: {todos.get().length}</p>
|
|
64
|
+
<button onClick={() => todos.insert({ id: Date.now(), text: 'New todo', done: false })}>
|
|
65
|
+
Add Todo
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
72
69
|
}
|
|
73
70
|
```
|
|
74
71
|
|
|
75
72
|
## Core Concepts
|
|
76
73
|
|
|
77
|
-
###
|
|
74
|
+
### State Access Patterns
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
Every state property gets these core methods:
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
#### Primitives (strings, numbers, booleans)
|
|
79
|
+
|
|
80
|
+
- `.get()` - read values reactively
|
|
81
|
+
- `.update()` - set values
|
|
82
|
+
- `.toggle()` - flip booleans
|
|
83
|
+
- `.$get()` - non-reactive read (signals)
|
|
84
|
+
- `.$derive()` - computed signals
|
|
85
|
+
|
|
86
|
+
#### Objects
|
|
87
|
+
|
|
88
|
+
- All primitive methods plus access to nested properties
|
|
89
|
+
- `.update()` can do partial updates
|
|
90
|
+
|
|
91
|
+
#### Arrays
|
|
92
|
+
|
|
93
|
+
- All core methods plus array-specific operations
|
|
94
|
+
- Built-in selection tracking and metadata
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
const allSettings = settings.get();
|
|
96
|
+
### Reading State
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
```typescript
|
|
99
|
+
const user = useCogsState('user');
|
|
100
|
+
const todos = useCogsState('todos');
|
|
101
|
+
const settings = useCogsState('settings');
|
|
102
|
+
|
|
103
|
+
// Reactive reads (triggers re-renders)
|
|
104
|
+
const userName = user.name.get();
|
|
105
|
+
const allTodos = todos.get();
|
|
88
106
|
const isDarkMode = settings.darkMode.get();
|
|
89
107
|
|
|
90
|
-
//
|
|
91
|
-
const
|
|
108
|
+
// Access nested properties
|
|
109
|
+
const counterValue = user.stats.counter.get();
|
|
110
|
+
const firstTodo = todos.index(0)?.get();
|
|
111
|
+
|
|
112
|
+
// Non-reactive reads (no re-renders, for signals)
|
|
113
|
+
const userNameStatic = user.name.$get();
|
|
92
114
|
|
|
93
|
-
//
|
|
94
|
-
const
|
|
115
|
+
// Computed signals (transforms value without re-renders)
|
|
116
|
+
const todoCount = todos.$derive((todos) => todos.length);
|
|
95
117
|
```
|
|
96
118
|
|
|
97
119
|
### Updating State
|
|
98
120
|
|
|
99
|
-
Updating state is done with methods like `.update()`, `.insert()`, and `.cut()`.
|
|
100
|
-
|
|
101
121
|
```typescript
|
|
102
|
-
|
|
103
|
-
settings
|
|
122
|
+
const user = useCogsState('user');
|
|
123
|
+
const settings = useCogsState('settings');
|
|
124
|
+
const todos = useCogsState('todos');
|
|
125
|
+
|
|
126
|
+
// Direct updates
|
|
127
|
+
user.name.update('Jane');
|
|
128
|
+
settings.darkMode.toggle();
|
|
104
129
|
|
|
105
|
-
// Functional
|
|
106
|
-
|
|
130
|
+
// Functional updates
|
|
131
|
+
user.stats.counter.update((prev) => prev + 1);
|
|
107
132
|
|
|
108
|
-
//
|
|
109
|
-
|
|
133
|
+
// Object updates
|
|
134
|
+
user.update((prev) => ({ ...prev, name: 'Jane', age: 30 }));
|
|
135
|
+
|
|
136
|
+
// Deep nested updates
|
|
137
|
+
todos.index(0).text.update('Updated todo text');
|
|
110
138
|
```
|
|
111
139
|
|
|
112
140
|
## Working with Arrays
|
|
113
141
|
|
|
114
|
-
|
|
142
|
+
Arrays are first-class citizens with powerful built-in operations:
|
|
115
143
|
|
|
116
144
|
### Basic Array Operations
|
|
117
145
|
|
|
118
146
|
```typescript
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
// Add
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
});
|
|
147
|
+
const todos = useCogsState('todos');
|
|
148
|
+
|
|
149
|
+
// Add items
|
|
150
|
+
todos.insert({ id: 'uuid', text: 'New todo', done: false });
|
|
151
|
+
todos.insert(({ uuid }) => ({
|
|
152
|
+
id: uuid,
|
|
153
|
+
text: 'Auto-generated ID',
|
|
154
|
+
done: false,
|
|
155
|
+
}));
|
|
156
|
+
|
|
157
|
+
// Remove items
|
|
158
|
+
todos.cut(2); // Remove at index 2
|
|
159
|
+
todos.cutSelected(); // Remove currently selected item
|
|
160
|
+
|
|
161
|
+
// Access items
|
|
162
|
+
const firstTodo = todos.index(0);
|
|
163
|
+
const lastTodo = todos.last();
|
|
164
|
+
```
|
|
128
165
|
|
|
129
|
-
|
|
130
|
-
cart.items.cut(2);
|
|
166
|
+
### Array Iteration and Rendering
|
|
131
167
|
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
#### `stateMap()` - Enhanced Array Mapping
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const todos = useCogsState('todos');
|
|
172
|
+
|
|
173
|
+
// Returns transformed array, each item is a full state object
|
|
174
|
+
const todoElements = todos.stateMap((todoState, index, arrayState) => (
|
|
175
|
+
<TodoItem
|
|
176
|
+
key={todoState.id.get()}
|
|
177
|
+
todo={todoState}
|
|
178
|
+
onToggle={() => todoState.done.toggle()}
|
|
179
|
+
onDelete={() => arrayState.cut(index)}
|
|
180
|
+
/>
|
|
181
|
+
));
|
|
134
182
|
```
|
|
135
183
|
|
|
136
|
-
|
|
184
|
+
#### `stateList()` - JSX List Rendering
|
|
137
185
|
|
|
138
186
|
```typescript
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
187
|
+
const todos = useCogsState('todos');
|
|
188
|
+
|
|
189
|
+
// Renders directly in place with automatic key management
|
|
190
|
+
{todos.stateList((todoState, index, arrayState) => (
|
|
191
|
+
<div key={todoState.id.get()}>
|
|
192
|
+
<span>{todoState.text.get()}</span>
|
|
193
|
+
<button onClick={() => todoState.done.toggle()}>Toggle</button>
|
|
194
|
+
<button onClick={() => arrayState.cut(index)}>Delete</button>
|
|
195
|
+
</div>
|
|
196
|
+
))}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### `$stateMap()` - Signal-Based Rendering
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const todos = useCogsState('todos');
|
|
203
|
+
|
|
204
|
+
// Most efficient - updates only changed items, no React re-renders
|
|
205
|
+
{todos.$stateMap((todoState, index, arrayState) => (
|
|
206
|
+
<TodoItem todo={todoState} />
|
|
207
|
+
))}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Advanced Array Methods
|
|
211
|
+
|
|
212
|
+
#### Filtering and Sorting
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
const todos = useCogsState('todos');
|
|
216
|
+
|
|
217
|
+
// Filter items (returns new state object with filtered view)
|
|
218
|
+
const completedTodos = todos.stateFilter((todo) => todo.done);
|
|
219
|
+
const incompleteTodos = todos.stateFilter((todo) => !todo.done);
|
|
220
|
+
|
|
221
|
+
// Sort items (returns new state object with sorted view)
|
|
222
|
+
const sortedTodos = todos.stateSort((a, b) => a.text.localeCompare(b.text));
|
|
223
|
+
|
|
224
|
+
// Chain operations
|
|
225
|
+
const sortedCompletedTodos = todos
|
|
226
|
+
.stateFilter((todo) => todo.done)
|
|
227
|
+
.stateSort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### Finding and Searching
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const todos = useCogsState('todos');
|
|
234
|
+
|
|
235
|
+
// Find by property value
|
|
236
|
+
const todoById = todos.findWith('id', 'some-id');
|
|
237
|
+
if (todoById) {
|
|
238
|
+
todoById.text.update('Updated text');
|
|
147
239
|
}
|
|
240
|
+
|
|
241
|
+
// Find with custom function
|
|
242
|
+
const firstIncompleteTodo = todos.stateFind((todo) => !todo.done);
|
|
148
243
|
```
|
|
149
244
|
|
|
150
|
-
|
|
245
|
+
#### Unique Operations
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const todos = useCogsState('todos');
|
|
249
|
+
|
|
250
|
+
// Insert only if unique (prevents duplicates)
|
|
251
|
+
todos.uniqueInsert(
|
|
252
|
+
{ id: 'new-id', text: 'New todo', done: false },
|
|
253
|
+
['id'], // Fields to check for uniqueness
|
|
254
|
+
(existingItem) => {
|
|
255
|
+
// Optional: callback if match found
|
|
256
|
+
return { ...existingItem, text: 'Updated existing' };
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Toggle presence (insert if missing, remove if present)
|
|
261
|
+
todos.toggleByValue('some-id');
|
|
262
|
+
```
|
|
151
263
|
|
|
152
|
-
|
|
264
|
+
#### Selection Management
|
|
153
265
|
|
|
154
266
|
```typescript
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
return (
|
|
162
|
-
<div>
|
|
163
|
-
<h3>Products</h3>
|
|
164
|
-
{cart.items.stateMap((item, itemUpdater) => (
|
|
165
|
-
<div
|
|
166
|
-
key={item.id}
|
|
167
|
-
// Use `_selected` to apply styling
|
|
168
|
-
className={itemUpdater.\_selected ? "product selected" : "product"}
|
|
169
|
-
// Use `toggleSelected` to handle clicks
|
|
170
|
-
onClick={() => itemUpdater.toggleSelected()} >
|
|
171
|
-
{item.name}
|
|
172
|
-
</div>
|
|
173
|
-
))}
|
|
267
|
+
const todos = useCogsState('todos');
|
|
268
|
+
|
|
269
|
+
// Built-in selection tracking
|
|
270
|
+
const selectedTodo = todos.getSelected();
|
|
271
|
+
const selectedIndex = todos.getSelectedIndex();
|
|
174
272
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
273
|
+
// Set selection on individual items
|
|
274
|
+
todos.index(0).setSelected(true);
|
|
275
|
+
todos.index(0).toggleSelected();
|
|
276
|
+
|
|
277
|
+
// Clear all selections
|
|
278
|
+
todos.clearSelected();
|
|
279
|
+
|
|
280
|
+
// Check if item is selected
|
|
281
|
+
const isSelected = todos.index(0).isSelected;
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Virtualization for Large Lists
|
|
285
|
+
|
|
286
|
+
For performance with large datasets:
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
function MessageList() {
|
|
290
|
+
const messages = useCogsState('messages', { reactiveType: 'none' });
|
|
291
|
+
|
|
292
|
+
const { virtualState, virtualizerProps, scrollToBottom } =
|
|
293
|
+
messages.useVirtualView({
|
|
294
|
+
itemHeight: 65, // Height per item
|
|
295
|
+
overscan: 10, // Items to render outside viewport
|
|
296
|
+
stickToBottom: true, // Auto-scroll to bottom
|
|
297
|
+
scrollStickTolerance: 75 // Distance tolerance for bottom detection
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<div {...virtualizerProps.outer} className="h-96 overflow-auto">
|
|
302
|
+
<div style={virtualizerProps.inner.style}>
|
|
303
|
+
<div style={virtualizerProps.list.style}>
|
|
304
|
+
{virtualState.stateList((messageState, index) => (
|
|
305
|
+
<MessageItem key={messageState.id.get()} message={messageState} />
|
|
306
|
+
))}
|
|
182
307
|
</div>
|
|
183
|
-
|
|
308
|
+
</div>
|
|
184
309
|
</div>
|
|
185
|
-
|
|
186
|
-
);
|
|
310
|
+
);
|
|
187
311
|
}
|
|
188
312
|
```
|
|
189
313
|
|
|
190
|
-
###
|
|
314
|
+
### Streaming for Real-time Data
|
|
191
315
|
|
|
192
316
|
```typescript
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
)
|
|
317
|
+
const messages = useCogsState('messages');
|
|
318
|
+
|
|
319
|
+
// Create a stream for efficient batch operations
|
|
320
|
+
const messageStream = messages.stream({
|
|
321
|
+
bufferSize: 100, // Buffer size before auto-flush
|
|
322
|
+
flushInterval: 100, // Auto-flush interval (ms)
|
|
323
|
+
bufferStrategy: 'sliding', // 'sliding' | 'dropping' | 'accumulate'
|
|
324
|
+
store: (buffer) => buffer, // Transform buffered items before insertion
|
|
325
|
+
onFlush: (buffer) => console.log('Flushed', buffer.length, 'items'),
|
|
326
|
+
});
|
|
203
327
|
|
|
204
|
-
//
|
|
205
|
-
|
|
328
|
+
// Write individual items
|
|
329
|
+
messageStream.write(newMessage);
|
|
206
330
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
);
|
|
331
|
+
// Write multiple items
|
|
332
|
+
messageStream.writeMany([msg1, msg2, msg3]);
|
|
333
|
+
|
|
334
|
+
// Manual flush
|
|
335
|
+
messageStream.flush();
|
|
212
336
|
|
|
213
|
-
//
|
|
214
|
-
|
|
337
|
+
// Pause/resume
|
|
338
|
+
messageStream.pause();
|
|
339
|
+
messageStream.resume();
|
|
340
|
+
|
|
341
|
+
// Close stream
|
|
342
|
+
messageStream.close();
|
|
215
343
|
```
|
|
216
344
|
|
|
217
345
|
## Reactivity Control
|
|
218
346
|
|
|
219
|
-
Cogsbox offers different
|
|
347
|
+
Cogsbox offers different reactivity modes for performance optimization:
|
|
220
348
|
|
|
221
349
|
### Component Reactivity (Default)
|
|
222
350
|
|
|
223
|
-
The component re-renders whenever any state value accessed within it (using `.get()`) changes.
|
|
224
|
-
|
|
225
351
|
```typescript
|
|
226
|
-
//
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
<div>
|
|
232
|
-
<div>Items: {cart.items.get().length}</div>
|
|
233
|
-
<div>Total: {cart.total.get()}</div>
|
|
234
|
-
</div>
|
|
235
|
-
);
|
|
352
|
+
// Re-renders when any accessed state changes
|
|
353
|
+
const user = useCogsState('user');
|
|
354
|
+
// or explicitly:
|
|
355
|
+
const user = useCogsState('user', { reactiveType: 'component' });
|
|
236
356
|
```
|
|
237
357
|
|
|
238
358
|
### Dependency-Based Reactivity
|
|
239
359
|
|
|
240
|
-
The component re-renders _only_ when the values returned by `reactiveDeps` change.
|
|
241
|
-
|
|
242
360
|
```typescript
|
|
243
|
-
// Only re-renders when
|
|
244
|
-
const
|
|
245
|
-
reactiveType:
|
|
246
|
-
reactiveDeps: (state) => [state.
|
|
361
|
+
// Only re-renders when specified dependencies change
|
|
362
|
+
const user = useCogsState('user', {
|
|
363
|
+
reactiveType: 'deps',
|
|
364
|
+
reactiveDeps: (state) => [state.name, state.stats.counter],
|
|
247
365
|
});
|
|
248
366
|
```
|
|
249
367
|
|
|
250
368
|
### Full Reactivity
|
|
251
369
|
|
|
252
|
-
The component re-renders on _any_ change to the state slice, even for properties not accessed in the component. Use with caution.
|
|
253
|
-
|
|
254
370
|
```typescript
|
|
255
|
-
// Re-renders on
|
|
256
|
-
const
|
|
257
|
-
reactiveType: ["all"],
|
|
258
|
-
});
|
|
371
|
+
// Re-renders on ANY change to the state slice
|
|
372
|
+
const user = useCogsState('user', { reactiveType: 'all' });
|
|
259
373
|
```
|
|
260
374
|
|
|
261
|
-
###
|
|
375
|
+
### No Reactivity
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
// Never re-renders (useful with signals)
|
|
379
|
+
const todos = useCogsState('todos', { reactiveType: 'none' });
|
|
380
|
+
```
|
|
262
381
|
|
|
263
|
-
|
|
382
|
+
### Multiple Reactivity Types
|
|
264
383
|
|
|
265
384
|
```typescript
|
|
266
|
-
//
|
|
267
|
-
|
|
385
|
+
// Combine multiple reactivity modes
|
|
386
|
+
const user = useCogsState('user', {
|
|
387
|
+
reactiveType: ['component', 'deps'],
|
|
388
|
+
reactiveDeps: (state) => [state.online],
|
|
389
|
+
});
|
|
390
|
+
```
|
|
268
391
|
|
|
269
|
-
|
|
270
|
-
{/* $derive transforms the value before rendering */}
|
|
271
|
-
<div>Items: {cart.items.$derive(items => items.length)}</div>
|
|
392
|
+
## Signal-Based Updates
|
|
272
393
|
|
|
273
|
-
|
|
274
|
-
<div>Total: {cart.total.$get()}</div>
|
|
394
|
+
The most efficient rendering method - bypasses React entirely:
|
|
275
395
|
|
|
276
|
-
|
|
277
|
-
)
|
|
396
|
+
```typescript
|
|
397
|
+
function PerformantComponent() {
|
|
398
|
+
const user = useCogsState('user', { reactiveType: 'none' });
|
|
399
|
+
const todos = useCogsState('todos', { reactiveType: 'none' });
|
|
400
|
+
|
|
401
|
+
return (
|
|
402
|
+
<div>
|
|
403
|
+
{/* These update DOM directly, no React re-renders */}
|
|
404
|
+
<div>Name: {user.name.$get()}</div>
|
|
405
|
+
<div>Counter: {user.stats.counter.$get()}</div>
|
|
406
|
+
<div>Todo Count: {todos.$derive(todos => todos.length)}</div>
|
|
407
|
+
|
|
408
|
+
{/* Signal-based list rendering */}
|
|
409
|
+
{todos.$stateMap((todo, index) => (
|
|
410
|
+
<div key={todo.id.$get()}>
|
|
411
|
+
<span>{todo.text.$get()}</span>
|
|
412
|
+
<button onClick={() => todo.done.toggle()}>Toggle</button>
|
|
413
|
+
</div>
|
|
414
|
+
))}
|
|
415
|
+
|
|
416
|
+
{/* Wrap with formElement for isolated reactivity */}
|
|
417
|
+
{user.stats.counter.formElement((obj) => (
|
|
418
|
+
<button onClick={() => obj.update(prev => prev + 1)}>
|
|
419
|
+
Increment: {obj.get()}
|
|
420
|
+
</button>
|
|
421
|
+
))}
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
278
425
|
```
|
|
279
426
|
|
|
280
|
-
##
|
|
427
|
+
## Form Management
|
|
281
428
|
|
|
282
|
-
Cogsbox
|
|
429
|
+
Cogsbox excels at form handling with automatic debouncing and validation:
|
|
430
|
+
|
|
431
|
+
### Basic Form Elements
|
|
283
432
|
|
|
284
433
|
```typescript
|
|
285
434
|
import { z } from 'zod';
|
|
286
|
-
import { createCogsState } from 'cogsbox-state';
|
|
287
435
|
|
|
288
|
-
// Define
|
|
436
|
+
// Define validation schema
|
|
289
437
|
const userSchema = z.object({
|
|
290
|
-
name: z.string().min(1, "Name is required"),
|
|
291
|
-
email: z.string().email("
|
|
292
|
-
age: z.number().min(18, "
|
|
438
|
+
name: z.string().min(1, "Name is required"),
|
|
439
|
+
email: z.string().email("Invalid email"),
|
|
440
|
+
age: z.number().min(18, "Must be 18+")
|
|
293
441
|
});
|
|
294
442
|
|
|
295
|
-
// Create state with validation
|
|
296
|
-
|
|
297
|
-
userForm: {
|
|
298
|
-
initialState: {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
</div>
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
}
|
|
443
|
+
// Create state with validation
|
|
444
|
+
const { useCogsState } = createCogsState({
|
|
445
|
+
userForm: {
|
|
446
|
+
initialState: { name: "", email: "", age: 18 },
|
|
447
|
+
validation: {
|
|
448
|
+
key: "userValidation",
|
|
449
|
+
zodSchema: userSchema,
|
|
450
|
+
onBlur: true // Validate on blur
|
|
451
|
+
},
|
|
452
|
+
formElements: {
|
|
453
|
+
validation: ({ children, active, message }) => (
|
|
454
|
+
<div className="form-field">
|
|
455
|
+
{children}
|
|
456
|
+
{active && <span className="error">{message}</span>}
|
|
457
|
+
</div>
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
}
|
|
317
461
|
});
|
|
318
462
|
|
|
319
463
|
function UserForm() {
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
const handleSubmit = (e) => {
|
|
323
|
-
e.preventDefault();
|
|
324
|
-
// Run all validations
|
|
325
|
-
if (user.validateZodSchema()) {
|
|
326
|
-
console.log("Form is valid!", user.get());
|
|
327
|
-
} else {
|
|
328
|
-
console.log("Form has errors.");
|
|
329
|
-
}
|
|
330
|
-
};
|
|
464
|
+
const userForm = useCogsState('userForm');
|
|
331
465
|
|
|
332
|
-
return (
|
|
333
|
-
<form
|
|
334
|
-
{
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
<input
|
|
339
|
-
</>
|
|
340
|
-
))}
|
|
466
|
+
return (
|
|
467
|
+
<form>
|
|
468
|
+
{/* Auto-debounced input with validation wrapper */}
|
|
469
|
+
{userForm.name.formElement(({ inputProps }) => (
|
|
470
|
+
<>
|
|
471
|
+
<label>Name</label>
|
|
472
|
+
<input {...inputProps} />
|
|
473
|
+
</>
|
|
474
|
+
))}
|
|
341
475
|
|
|
342
|
-
{
|
|
476
|
+
{/* Custom debounce time */}
|
|
477
|
+
{userForm.email.formElement(({ inputProps, get, update }) => (
|
|
343
478
|
<>
|
|
344
479
|
<label>Email</label>
|
|
345
|
-
<input
|
|
480
|
+
<input {...inputProps} />
|
|
481
|
+
<small>Current: {get()}</small>
|
|
346
482
|
</>
|
|
347
|
-
))}
|
|
483
|
+
), { debounceTime: 500 })}
|
|
348
484
|
|
|
349
|
-
{
|
|
485
|
+
{/* Custom form control */}
|
|
486
|
+
{userForm.age.formElement(({ get, update }) => (
|
|
350
487
|
<>
|
|
351
488
|
<label>Age</label>
|
|
352
|
-
<input
|
|
489
|
+
<input
|
|
490
|
+
type="number"
|
|
491
|
+
value={get()}
|
|
492
|
+
onChange={e => update(parseInt(e.target.value))}
|
|
493
|
+
/>
|
|
353
494
|
</>
|
|
354
495
|
))}
|
|
355
496
|
|
|
356
|
-
<button
|
|
497
|
+
<button onClick={() => {
|
|
498
|
+
if (userForm.validateZodSchema()) {
|
|
499
|
+
console.log('Valid!', userForm.get());
|
|
500
|
+
}
|
|
501
|
+
}}>
|
|
502
|
+
Submit
|
|
503
|
+
</button>
|
|
357
504
|
</form>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
```
|
|
358
508
|
|
|
359
|
-
|
|
509
|
+
## Advanced Features
|
|
510
|
+
|
|
511
|
+
### Server Synchronization
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
const { useCogsState } = createCogsState({
|
|
515
|
+
userProfile: {
|
|
516
|
+
initialState: { name: "", email: "" },
|
|
517
|
+
sync: {
|
|
518
|
+
action: async (state) => {
|
|
519
|
+
const response = await fetch('/api/user', {
|
|
520
|
+
method: 'PUT',
|
|
521
|
+
body: JSON.stringify(state)
|
|
522
|
+
});
|
|
523
|
+
return response.ok
|
|
524
|
+
? { success: true, data: await response.json() }
|
|
525
|
+
: { success: false, error: 'Failed to save' };
|
|
526
|
+
},
|
|
527
|
+
onSuccess: (data) => console.log('Saved!', data),
|
|
528
|
+
onError: (error) => console.error('Save failed:', error)
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
function UserProfile() {
|
|
534
|
+
const userProfile = useCogsState('userProfile');
|
|
535
|
+
|
|
536
|
+
return (
|
|
537
|
+
<div>
|
|
538
|
+
<div>Status: {userProfile.getStatus()}</div> {/* 'fresh' | 'dirty' | 'synced' | 'restored' */}
|
|
539
|
+
<input
|
|
540
|
+
value={userProfile.name.get()}
|
|
541
|
+
onChange={e => userProfile.name.update(e.target.value)}
|
|
542
|
+
/>
|
|
543
|
+
<button onClick={() => userProfile.sync()}>Save to Server</button>
|
|
544
|
+
</div>
|
|
545
|
+
);
|
|
360
546
|
}
|
|
361
547
|
```
|
|
548
|
+
|
|
549
|
+
### Local Storage Integration
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
const { useCogsState } = createCogsState({
|
|
553
|
+
userPrefs: {
|
|
554
|
+
initialState: { theme: 'dark', language: 'en' },
|
|
555
|
+
localStorage: {
|
|
556
|
+
key: 'user-preferences',
|
|
557
|
+
onChange: (state) => console.log('Saved to localStorage:', state),
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
function PreferencesComponent() {
|
|
563
|
+
const userPrefs = useCogsState('userPrefs');
|
|
564
|
+
|
|
565
|
+
return (
|
|
566
|
+
<div>
|
|
567
|
+
<select
|
|
568
|
+
value={userPrefs.theme.get()}
|
|
569
|
+
onChange={e => userPrefs.theme.update(e.target.value)}
|
|
570
|
+
>
|
|
571
|
+
<option value="dark">Dark</option>
|
|
572
|
+
<option value="light">Light</option>
|
|
573
|
+
</select>
|
|
574
|
+
</div>
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### State Status and History
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
const user = useCogsState('user');
|
|
583
|
+
|
|
584
|
+
// Check what changed from initial state
|
|
585
|
+
const differences = user.getDifferences();
|
|
586
|
+
|
|
587
|
+
// Get current status
|
|
588
|
+
const status = user.getStatus(); // 'fresh' | 'dirty' | 'synced' | 'restored'
|
|
589
|
+
|
|
590
|
+
// Revert to initial state
|
|
591
|
+
user.revertToInitialState();
|
|
592
|
+
|
|
593
|
+
// Update initial state (useful for findign diffs and server-synced data)
|
|
594
|
+
const newServerData = {
|
|
595
|
+
name: 'Jane Doe',
|
|
596
|
+
age: 31,
|
|
597
|
+
stats: { counter: 100, lastUpdated: new Date() },
|
|
598
|
+
};
|
|
599
|
+
user.updateInitialState(newServerData);
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### Component Isolation
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
// Each component can have its own reactive settings
|
|
606
|
+
function ComponentA() {
|
|
607
|
+
const user = useCogsState('user', {
|
|
608
|
+
reactiveType: 'deps',
|
|
609
|
+
reactiveDeps: (state) => [state.name],
|
|
610
|
+
});
|
|
611
|
+
// Only re-renders when user.name changes
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function ComponentB() {
|
|
615
|
+
const user = useCogsState('user', {
|
|
616
|
+
reactiveType: 'all',
|
|
617
|
+
});
|
|
618
|
+
// Re-renders on any change to 'user' state
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## Performance Tips
|
|
623
|
+
|
|
624
|
+
1. **Use signals for high-frequency updates**: `.$get()` and `.$derive()` don't trigger React re-renders
|
|
625
|
+
2. **Use `reactiveType: 'none'` with signals**: Maximum performance for signal-heavy components
|
|
626
|
+
3. **Use virtualization for large lists**: `useVirtualView()` handles thousands of items efficiently
|
|
627
|
+
4. **Use streaming for real-time data**: Batch operations with `stream()` for better performance
|
|
628
|
+
5. **Chain filter/sort operations**: `stateFilter().stateSort()` creates efficient views
|
|
629
|
+
6. **Use `formElement` for forms**: Automatic debouncing and validation handling
|
|
630
|
+
|
|
631
|
+
## Common Patterns
|
|
632
|
+
|
|
633
|
+
### Master-Detail Interface
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
function TodoApp() {
|
|
637
|
+
const todos = useCogsState('todos');
|
|
638
|
+
const selectedTodo = todos.getSelected();
|
|
639
|
+
|
|
640
|
+
return (
|
|
641
|
+
<div className="flex">
|
|
642
|
+
<div className="list">
|
|
643
|
+
{todos.stateList((todo, index) => (
|
|
644
|
+
<div
|
|
645
|
+
key={todo.id.get()}
|
|
646
|
+
className={todo.isSelected ? 'selected' : ''}
|
|
647
|
+
onClick={() => todo.toggleSelected()}
|
|
648
|
+
>
|
|
649
|
+
{todo.text.get()}
|
|
650
|
+
</div>
|
|
651
|
+
))}
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
<div className="detail">
|
|
655
|
+
{selectedTodo ? (
|
|
656
|
+
<TodoDetail todo={selectedTodo} />
|
|
657
|
+
) : (
|
|
658
|
+
<p>Select a todo</p>
|
|
659
|
+
)}
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### Real-time Chat with Virtualization
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
function ChatRoom() {
|
|
670
|
+
const messages = useCogsState('messages', { reactiveType: 'none' });
|
|
671
|
+
|
|
672
|
+
const { virtualState, virtualizerProps, scrollToBottom } =
|
|
673
|
+
messages.useVirtualView({
|
|
674
|
+
itemHeight: 65,
|
|
675
|
+
overscan: 10,
|
|
676
|
+
stickToBottom: true,
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
return (
|
|
680
|
+
<div {...virtualizerProps.outer} className="chat-container">
|
|
681
|
+
<div style={virtualizerProps.inner.style}>
|
|
682
|
+
<div style={virtualizerProps.list.style}>
|
|
683
|
+
{virtualState.stateList((message) => (
|
|
684
|
+
<MessageItem key={message.id.$get()} message={message} />
|
|
685
|
+
))}
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
### Multiple State Slices in One Component
|
|
694
|
+
|
|
695
|
+
```typescript
|
|
696
|
+
function Dashboard() {
|
|
697
|
+
const user = useCogsState('user');
|
|
698
|
+
const todos = useCogsState('todos');
|
|
699
|
+
const settings = useCogsState('settings');
|
|
700
|
+
|
|
701
|
+
return (
|
|
702
|
+
<div>
|
|
703
|
+
<header>
|
|
704
|
+
<h1>Welcome, {user.name.get()}</h1>
|
|
705
|
+
<button onClick={() => settings.darkMode.toggle()}>
|
|
706
|
+
Toggle Theme
|
|
707
|
+
</button>
|
|
708
|
+
</header>
|
|
709
|
+
|
|
710
|
+
<main>
|
|
711
|
+
<p>You have {todos.get().length} todos</p>
|
|
712
|
+
<p>Counter: {user.stats.counter.get()}</p>
|
|
713
|
+
</main>
|
|
714
|
+
</div>
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
This library provides a unique approach to React state management by creating a proxy that mirrors your data structure while adding powerful methods for manipulation, rendering, and performance optimization.
|