or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

component-system.mdcontext-scoping.mdcontrol-flow.mdindex.mdreactive-primitives.mdresources-async.mdstore-management.mdweb-rendering.md

store-management.mddocs/

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

```