0
# Store Management
1
2
Nested reactive state management system with proxy-based stores, mutations, and advanced reconciliation for managing complex application state.
3
4
## Capabilities
5
6
### Creating Stores
7
8
Create reactive stores for managing nested and complex state structures.
9
10
```typescript { .api }
11
/**
12
* Creates a reactive store that can be read through a proxy object and written with a setter function
13
* @param store - Initial store value or existing store
14
* @param options - Configuration options
15
* @returns Tuple of [store getter proxy, store setter function]
16
*/
17
function createStore<T extends object = {}>(
18
...[store, options]: {} extends T
19
? [store?: T | Store<T>, options?: { name?: string }]
20
: [store: T | Store<T>, options?: { name?: string }]
21
): [get: Store<T>, set: SetStoreFunction<T>];
22
23
/**
24
* Returns the underlying data in the store without a proxy
25
* @param item - Store or value to unwrap
26
* @param set - Optional set to track unwrapped objects
27
* @returns Unwrapped data
28
*/
29
function unwrap<T>(item: T, set?: Set<unknown>): T;
30
31
interface StoreNode {
32
[$NODE]?: DataNodes;
33
[key: PropertyKey]: any;
34
}
35
36
type NotWrappable = string | number | bigint | symbol | boolean | Function | null | undefined;
37
type Store<T> = T;
38
```
39
40
**Usage Examples:**
41
42
```typescript
43
import { createStore, unwrap } from "solid-js/store";
44
45
// Basic store creation
46
const [user, setUser] = createStore({
47
name: "John Doe",
48
email: "john@example.com",
49
profile: {
50
age: 30,
51
bio: "Software developer",
52
preferences: {
53
theme: "dark",
54
notifications: true
55
}
56
},
57
posts: []
58
});
59
60
// Reading from store (reactive)
61
console.log(user.name); // "John Doe"
62
console.log(user.profile.age); // 30
63
64
// Updating store values
65
setUser("name", "Jane Doe");
66
setUser("profile", "age", 31);
67
setUser("profile", "preferences", "theme", "light");
68
69
// Functional updates
70
setUser("profile", "age", age => age + 1);
71
72
// Adding to arrays
73
setUser("posts", posts => [...posts, { id: 1, title: "First Post" }]);
74
75
// Unwrapping store data (removes reactivity)
76
const rawUserData = unwrap(user);
77
console.log(rawUserData); // Plain JavaScript object
78
```
79
80
### Advanced Store Operations
81
82
Perform complex updates with nested path setting and batch operations.
83
84
```typescript { .api }
85
interface SetStoreFunction<T> {
86
// Set entire store
87
(value: T): void;
88
(setter: (prev: T) => T): void;
89
90
// Set by key
91
<K extends keyof T>(key: K, value: T[K]): void;
92
<K extends keyof T>(key: K, setter: (prev: T[K]) => T[K]): void;
93
94
// Set by nested path
95
<K1 extends keyof T, K2 extends keyof T[K1]>(
96
key1: K1,
97
key2: K2,
98
value: T[K1][K2]
99
): void;
100
101
// Array operations
102
<K extends keyof T>(
103
key: K,
104
index: number,
105
value: T[K] extends readonly (infer U)[] ? U : never
106
): void;
107
108
// Conditional updates and more overloads...
109
}
110
```
111
112
**Usage Examples:**
113
114
```typescript
115
import { createStore } from "solid-js/store";
116
import { For, createEffect } from "solid-js";
117
118
function TodoApp() {
119
const [todos, setTodos] = createStore({
120
items: [
121
{ id: 1, text: "Learn Solid", completed: false },
122
{ id: 2, text: "Build an app", completed: false }
123
],
124
filter: "all" as "all" | "active" | "completed",
125
stats: {
126
total: 2,
127
active: 2,
128
completed: 0
129
}
130
});
131
132
// Update individual todo
133
const toggleTodo = (id: number) => {
134
setTodos(
135
"items",
136
item => item.id === id,
137
"completed",
138
completed => !completed
139
);
140
updateStats();
141
};
142
143
// Add new todo
144
const addTodo = (text: string) => {
145
const newTodo = {
146
id: Date.now(),
147
text,
148
completed: false
149
};
150
151
setTodos("items", items => [...items, newTodo]);
152
updateStats();
153
};
154
155
// Remove todo
156
const removeTodo = (id: number) => {
157
setTodos("items", items => items.filter(item => item.id !== id));
158
updateStats();
159
};
160
161
// Update statistics
162
const updateStats = () => {
163
setTodos("stats", {
164
total: todos.items.length,
165
active: todos.items.filter(item => !item.completed).length,
166
completed: todos.items.filter(item => item.completed).length
167
});
168
};
169
170
// Batch operations
171
const clearCompleted = () => {
172
setTodos("items", items => items.filter(item => !item.completed));
173
updateStats();
174
};
175
176
const toggleAll = () => {
177
const allCompleted = todos.items.every(item => item.completed);
178
setTodos("items", {}, "completed", !allCompleted);
179
updateStats();
180
};
181
182
return (
183
<div class="todo-app">
184
<h1>Todo App</h1>
185
186
<div class="stats">
187
<span>Total: {todos.stats.total}</span>
188
<span>Active: {todos.stats.active}</span>
189
<span>Completed: {todos.stats.completed}</span>
190
</div>
191
192
<div class="controls">
193
<button onClick={toggleAll}>Toggle All</button>
194
<button onClick={clearCompleted}>Clear Completed</button>
195
<select
196
value={todos.filter}
197
onChange={(e) => setTodos("filter", e.target.value as any)}
198
>
199
<option value="all">All</option>
200
<option value="active">Active</option>
201
<option value="completed">Completed</option>
202
</select>
203
</div>
204
205
<For each={filteredTodos()}>
206
{(todo) => (
207
<div class={`todo ${todo.completed ? "completed" : ""}`}>
208
<input
209
type="checkbox"
210
checked={todo.completed}
211
onChange={() => toggleTodo(todo.id)}
212
/>
213
<span>{todo.text}</span>
214
<button onClick={() => removeTodo(todo.id)}>Remove</button>
215
</div>
216
)}
217
</For>
218
</div>
219
);
220
221
function filteredTodos() {
222
switch (todos.filter) {
223
case "active":
224
return todos.items.filter(item => !item.completed);
225
case "completed":
226
return todos.items.filter(item => item.completed);
227
default:
228
return todos.items;
229
}
230
}
231
}
232
```
233
234
### Mutable Stores
235
236
Create mutable stores that can be modified directly like plain objects.
237
238
```typescript { .api }
239
/**
240
* Creates a mutable store that can be mutated directly
241
* @param state - Initial state
242
* @param options - Configuration options
243
* @returns Mutable store object
244
*/
245
function createMutable<T extends StoreNode>(
246
state: T,
247
options?: { name?: string }
248
): T;
249
250
/**
251
* Modifies a mutable store using a modifier function
252
* @param state - Mutable store to modify
253
* @param modifier - Function that modifies the state (mutates in place)
254
*/
255
function modifyMutable<T>(
256
state: T,
257
modifier: (state: T) => void
258
): void;
259
```
260
261
**Usage Examples:**
262
263
```typescript
264
import { createMutable, modifyMutable } from "solid-js/store";
265
import { createEffect } from "solid-js";
266
267
function MutableStoreExample() {
268
const state = createMutable({
269
user: {
270
name: "John",
271
age: 30,
272
preferences: {
273
theme: "dark",
274
language: "en"
275
}
276
},
277
items: [1, 2, 3]
278
});
279
280
// Direct mutation (triggers reactivity)
281
const updateUser = () => {
282
state.user.name = "Jane";
283
state.user.age++;
284
state.user.preferences.theme = state.user.preferences.theme === "dark" ? "light" : "dark";
285
};
286
287
// Array mutations
288
const addItem = () => {
289
state.items.push(state.items.length + 1);
290
};
291
292
const removeItem = () => {
293
state.items.pop();
294
};
295
296
// Using modifyMutable for complex updates
297
const resetState = () => {
298
modifyMutable(state, (draft) => {
299
draft.user.name = "Anonymous";
300
draft.user.age = 0;
301
draft.user.preferences = { theme: "light", language: "en" };
302
draft.items = [];
303
return draft;
304
});
305
};
306
307
// Reactive effects work with mutable stores
308
createEffect(() => {
309
console.log("User changed:", state.user.name, state.user.age);
310
});
311
312
createEffect(() => {
313
console.log("Items count:", state.items.length);
314
});
315
316
return (
317
<div>
318
<h2>Mutable Store Example</h2>
319
320
<div>
321
<p>User: {state.user.name} (Age: {state.user.age})</p>
322
<p>Theme: {state.user.preferences.theme}</p>
323
<p>Items: {state.items.join(", ")}</p>
324
</div>
325
326
<div>
327
<button onClick={updateUser}>Update User</button>
328
<button onClick={addItem}>Add Item</button>
329
<button onClick={removeItem}>Remove Item</button>
330
<button onClick={resetState}>Reset</button>
331
</div>
332
</div>
333
);
334
}
335
```
336
337
### Store Modifiers
338
339
Use advanced modifiers for efficient updates and reconciliation.
340
341
```typescript { .api }
342
/**
343
* Diff method for setStore to efficiently update nested data
344
* @param value - New value to reconcile with
345
* @param options - Reconciliation options (defaults to empty object)
346
* @returns Function that performs the reconciliation
347
*/
348
function reconcile<T extends U, U>(
349
value: T,
350
options: {
351
key?: string | null;
352
merge?: boolean;
353
} = {}
354
): (state: U) => T;
355
356
/**
357
* Immer-style mutation helper for stores
358
* @param fn - Function that mutates the draft state
359
* @returns Function that applies the mutations
360
*/
361
function produce<T>(
362
fn: (state: T) => void
363
): (state: T) => T;
364
```
365
366
**Usage Examples:**
367
368
```typescript
369
import { createStore, reconcile, produce } from "solid-js/store";
370
371
function AdvancedStoreExample() {
372
const [data, setData] = createStore({
373
users: [
374
{ id: 1, name: "John", posts: [] },
375
{ id: 2, name: "Jane", posts: [] }
376
],
377
settings: {
378
theme: "dark",
379
language: "en"
380
}
381
});
382
383
// Reconcile with new data (efficient updates)
384
const updateUsers = async () => {
385
const newUsers = await fetchUsers(); // Assume this returns updated user data
386
387
setData("users", reconcile(newUsers, { key: "id" }));
388
};
389
390
// Using produce for complex mutations
391
const addPostToUser = (userId: number, post: { title: string; content: string }) => {
392
setData(produce((draft) => {
393
const user = draft.users.find(u => u.id === userId);
394
if (user) {
395
user.posts.push({ id: Date.now(), ...post });
396
}
397
}));
398
};
399
400
// Reconcile with merge option
401
const updateSettings = (newSettings: Partial<typeof data.settings>) => {
402
setData("settings", reconcile(newSettings, { merge: true }));
403
};
404
405
// Complex nested updates with produce
406
const complexUpdate = () => {
407
setData(produce((draft) => {
408
// Multiple complex operations
409
draft.users.forEach(user => {
410
if (user.posts.length > 5) {
411
user.posts = user.posts.slice(-5); // Keep only last 5 posts
412
}
413
});
414
415
// Update settings
416
draft.settings.theme = draft.settings.theme === "dark" ? "light" : "dark";
417
418
// Add new user if needed
419
if (draft.users.length < 10) {
420
draft.users.push({
421
id: Date.now(),
422
name: `User ${draft.users.length + 1}`,
423
posts: []
424
});
425
}
426
}));
427
};
428
429
return (
430
<div>
431
<div class="controls">
432
<button onClick={updateUsers}>Update Users</button>
433
<button onClick={() => addPostToUser(1, { title: "New Post", content: "Content" })}>
434
Add Post to User 1
435
</button>
436
<button onClick={() => updateSettings({ language: "es" })}>
437
Change Language
438
</button>
439
<button onClick={complexUpdate}>Complex Update</button>
440
</div>
441
442
<div class="data">
443
<h3>Users ({data.users.length})</h3>
444
<For each={data.users}>
445
{(user) => (
446
<div class="user">
447
<h4>{user.name}</h4>
448
<p>Posts: {user.posts.length}</p>
449
</div>
450
)}
451
</For>
452
453
<h3>Settings</h3>
454
<p>Theme: {data.settings.theme}</p>
455
<p>Language: {data.settings.language}</p>
456
</div>
457
</div>
458
);
459
}
460
461
async function fetchUsers() {
462
// Simulate API call
463
return [
464
{ id: 1, name: "John Updated", posts: [{ id: 1, title: "Post 1" }] },
465
{ id: 2, name: "Jane Updated", posts: [] },
466
{ id: 3, name: "New User", posts: [] }
467
];
468
}
469
```
470
471
### Store Utilities and Debugging
472
473
Access raw data and debugging utilities for store management.
474
475
```typescript { .api }
476
/**
477
* Symbol for accessing raw store data
478
*/
479
const $RAW: unique symbol;
480
481
/**
482
* Symbol for accessing store nodes
483
*/
484
const $NODE: unique symbol;
485
486
/**
487
* Symbol for tracking store property access
488
*/
489
const $HAS: unique symbol;
490
491
/**
492
* Symbol for self-reference in stores
493
*/
494
const $SELF: unique symbol;
495
496
/**
497
* Checks if an object can be wrapped by the store proxy
498
* @param obj - Object to check
499
* @returns True if the object can be wrapped
500
*/
501
function isWrappable<T>(obj: T | NotWrappable): obj is T;
502
503
/**
504
* Development utilities for stores
505
*/
506
const DEV: {
507
$NODE: symbol;
508
isWrappable: (obj: any) => boolean;
509
hooks: {
510
onStoreNodeUpdate: OnStoreNodeUpdate | null;
511
};
512
} | undefined;
513
514
type OnStoreNodeUpdate = (node: any, property: string | number | symbol, value: any, prev: any) => void;
515
```
516
517
**Usage Examples:**
518
519
```typescript
520
import { createStore, unwrap, $RAW } from "solid-js/store";
521
522
function StoreDebugging() {
523
const [store, setStore] = createStore({
524
nested: {
525
data: { count: 0 },
526
array: [1, 2, 3]
527
}
528
});
529
530
// Access raw data using $RAW symbol
531
console.log(store[$RAW]); // Raw underlying data
532
533
// Unwrap for serialization
534
const serializeStore = () => {
535
const unwrapped = unwrap(store);
536
return JSON.stringify(unwrapped, null, 2);
537
};
538
539
// Compare wrapped vs unwrapped
540
const compareData = () => {
541
console.log("Wrapped:", store.nested.data);
542
console.log("Unwrapped:", unwrap(store.nested.data));
543
console.log("Are same reference:", store.nested.data === unwrap(store.nested.data)); // false
544
};
545
546
// Development hooks (only available in dev mode)
547
if (DEV) {
548
DEV.hooks.onStoreNodeUpdate = (node, property, value, prev) => {
549
console.log("Store update:", { node, property, value, prev });
550
};
551
}
552
553
return (
554
<div>
555
<h2>Store Debugging</h2>
556
557
<div>
558
<p>Count: {store.nested.data.count}</p>
559
<p>Array: [{store.nested.array.join(", ")}]</p>
560
</div>
561
562
<div>
563
<button onClick={() => setStore("nested", "data", "count", c => c + 1)}>
564
Increment Count
565
</button>
566
<button onClick={() => setStore("nested", "array", arr => [...arr, arr.length + 1])}>
567
Add to Array
568
</button>
569
<button onClick={compareData}>
570
Compare Data
571
</button>
572
</div>
573
574
<div>
575
<h3>Serialized Store:</h3>
576
<pre>{serializeStore()}</pre>
577
</div>
578
</div>
579
);
580
}
581
```
582
583
### Internal Store Functions
584
585
Advanced internal functions exported for library authors and debugging.
586
587
```typescript { .api }
588
/**
589
* Gets data nodes from a store target
590
* @param target - Store target object
591
* @param symbol - Symbol to access ($NODE or $HAS)
592
* @returns Data nodes for the target
593
*/
594
function getNodes(target: StoreNode, symbol: typeof $NODE | typeof $HAS): DataNodes;
595
596
/**
597
* Gets a specific node from data nodes
598
* @param nodes - Data nodes collection
599
* @param property - Property key to access
600
* @param value - Optional value for node creation
601
* @returns Data node for the property
602
*/
603
function getNode(nodes: DataNodes, property: PropertyKey, value?: any): DataNode;
604
605
/**
606
* Sets a property value on a store node
607
* @param state - Store node to update
608
* @param property - Property key to set
609
* @param value - Value to set
610
* @param deleting - Whether this is a deletion operation
611
*/
612
function setProperty(state: StoreNode, property: PropertyKey, value: any, deleting?: boolean): void;
613
614
/**
615
* Updates a nested path in the store
616
* @param current - Current store node
617
* @param path - Path array to update
618
* @param traversed - Properties already traversed
619
*/
620
function updatePath(current: StoreNode, path: any[], traversed?: PropertyKey[]): void;
621
```
622
623
## Types
624
625
### Store Types
626
627
```typescript { .api }
628
type Store<T> = T;
629
630
interface StoreNode {
631
[$NODE]?: DataNodes;
632
[key: PropertyKey]: any;
633
}
634
635
type NotWrappable = string | number | bigint | symbol | boolean | Function | null | undefined;
636
637
type DataNodes = Record<PropertyKey, DataNode>;
638
639
interface DataNode {
640
value: any;
641
listeners?: Set<Function>;
642
}
643
644
interface SetStoreFunction<T> {
645
// Root level updates
646
(value: T): void;
647
(setter: (prev: T) => T): void;
648
649
// Key-based updates
650
<K extends keyof T>(key: K, value: T[K]): void;
651
<K extends keyof T>(key: K, setter: (prev: T[K]) => T[K]): void;
652
653
// Nested path updates (multiple overloads for different depths)
654
<K1 extends keyof T, K2 extends keyof T[K1]>(
655
key1: K1,
656
key2: K2,
657
value: T[K1][K2]
658
): void;
659
660
// Conditional updates
661
<K extends keyof T>(
662
key: K,
663
predicate: (item: T[K], index: number) => boolean,
664
value: T[K]
665
): void;
666
}
667
```
668
669
### Reconcile Types
670
671
```typescript { .api }
672
interface ReconcileOptions {
673
key?: string | null;
674
merge?: boolean;
675
}
676
677
type ReconcileFunction<T, U = T> = (state: U) => T;
678
```