SuperAPI AI Directory

solid-syntax-stores

Frontend & websites

View repository

Updated 10-16 06:18

About this skill

solid-syntax-stores

Quick Reference

Import

// ALL store utilities come from "solid-js/store" — NEVER from "solid-js"
import { createStore, produce, reconcile, unwrap, createMutable } from "solid-js/store";

Store Primitives Overview

Primitive Purpose Import
createStore Proxy-based reactive state for nested data solid-js/store
setStore Path-syntax setter for surgical updates Returned by createStore
produce Immer-style mutation syntax solid-js/store
reconcile Data diffing for API responses solid-js/store
unwrap Strip reactive proxy to plain object solid-js/store
createMutable Direct mutation store (MobX/Vue-style) solid-js/store

Stores vs Signals Decision Tree

Question Use Signal Use Store
Single primitive value (string, number, boolean)? YES NO
Nested object with multiple properties? NO YES
Array of objects that update individually? NO YES
Need fine-grained tracking per property? NO YES
Simple toggle or counter? YES NO
Form with many fields? NO YES
API response data (objects/arrays)? NO YES

Rule of thumb: ALWAYS use createStore when your state is an object or array. ALWAYS use createSignal for primitives.

Stores vs Signals Syntax Comparison

Aspect Signal Store
Create const [val, setVal] = createSignal(0) const [store, setStore] = createStore({x: 0})
Read val() (function call) store.x (property access)
Write setVal(1) setStore("x", 1)
Nested update N/A setStore("a", "b", "c", value)
Reactivity unit Entire value Per property

Critical Warnings

NEVER destructure a store — it kills reactivity:

// WRONG — Reads value once, loses all reactive tracking
const { username } = store.users[0];
return <span>{username}</span>; // NEVER updates

// CORRECT — Access store properties in JSX directly
return <span>{store.users[0].username}</span>; // Fine-grained tracking

NEVER call a store property as a function — stores use property access, NOT function calls:

// WRONG — Signals use function calls, stores do NOT
return <span>{store.count()}</span>; // TypeError: store.count is not a function

// CORRECT — Direct property access
return <span>{store.count}</span>; // Tracked reactively via proxy

NEVER replace the entire store with spread — use path syntax for surgical updates:

// WRONG — React pattern: spread/replace destroys fine-grained tracking
setStore({ ...store, name: "Jane" }); // Replaces entire root object

// CORRECT — Path syntax updates only the targeted property
setStore("name", "Jane"); // Only "name" subscribers update

NEVER mutate store directly without createMutable — use setStore or produce:

// WRONG — Direct mutation on a createStore proxy does nothing reactive
store.count = 5; // Silently fails — no subscribers notified

// CORRECT — Use the setter
setStore("count", 5);

createStore

Creates a proxy-based reactive store with fine-grained tracking at the property level. Signals are created lazily — only when a property is first accessed within a tracking scope.

const [store, setStore] = createStore<T>(initialValue: T): [Store<T>, SetStoreFunction<T>];
const [store, setStore] = createStore({
  user: { firstName: "John", lastName: "Smith" },
  items: [
    { id: 1, text: "Learn SolidJS", done: false },
    { id: 2, text: "Build app", done: false },
  ],
  count: 0,
});

setStore Path Syntax

The setter uses a path-based syntax for surgical nested updates. See references/methods.md for complete API signatures.

Cheat Sheet

Operation Syntax
Set property setStore("key", value)
Nested property setStore("a", "b", "c", value)
Array item by index setStore("items", 0, "done", true)
Functional update setStore("count", (prev) => prev + 1)
Multiple indices setStore("items", [0, 2, 4], "done", true)
Range setStore("items", { from: 0, to: 5 }, "done", true)
Range with step setStore("items", { from: 0, to: 10, by: 2 }, "done", true)
Filter predicate setStore("items", (item) => !item.done, "done", true)
Append to array setStore("items", store.items.length, newItem)
Object merge setStore("user", { lastName: "Doe" }) (shallow merge)
Delete property setStore("key", undefined)

Object Merge Behavior

When the final value in a path is an object, it performs a shallow merge — existing properties are preserved, only specified properties change:

setStore("user", { lastName: "Doe" });
// firstName remains "John", only lastName changes to "Doe"

Store Utilities

produce: Immer-Style Mutations

Write mutation-style code that gets applied as reactive updates. See references/methods.md for signature.

setState(
  produce((s) => {
    s.user.name = "Jane";
    s.items.push({ id: 3, text: "Deploy", done: false });
    s.items[0].done = true;
  })
);

NEVER use produce with Sets or Maps — it only works with plain objects and arrays.

Combine produce with path syntax for scoped mutations:

setStore("items", 0, produce((item) => {
  item.text = "Updated text";
  item.done = true;
}));

reconcile: Data Diffing

Diffs new data against existing store, updating only what changed. ALWAYS use reconcile when syncing API responses into stores.

// Sync API response — only changed properties trigger updates
const response = await fetch("/api/todos");
const todos = await response.json();
setStore("todos", reconcile(todos));

Options: key (default "id") for item matching, merge (default false) for leaf-level diffing.

unwrap: Strip Reactive Proxy

Returns a plain JavaScript object. ALWAYS use unwrap before passing store data to non-SolidJS code.

const plain = unwrap(store);
JSON.stringify(plain);           // Safe serialization
thirdPartyLib.process(plain);    // No proxy interference

createMutable: Direct Mutation Store

Allows direct property mutation (MobX/Vue-style). Supports computed getters/setters.

const state = createMutable({
  firstName: "John",
  lastName: "Smith",
  get fullName() { return `${this.firstName} ${this.lastName}`; },
  set fullName(value: string) { [this.firstName, this.lastName] = value.split(" "); },
});

state.firstName = "Jane";  // Direct mutation — reactive

NEVER use createMutable as default choice. ALWAYS prefer createStore for unidirectional data flow. Use createMutable ONLY when migrating from MobX/Vue or wrapping mutable third-party APIs.


Computed Getters in Stores

const [store, setStore] = createStore({
  firstName: "John",
  lastName: "Smith",
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
});

// fullName updates reactively when firstName or lastName changes
return <span>{store.fullName}</span>;

SolidJS 2.x Store Changes

Aspect SolidJS 1.x SolidJS 2.x
Store setters Path-style ergonomics Draft-first by default
Derived stores Not supported createStore(fn) for derived-but-writable patterns

Both versions share the same core store concepts. Path syntax and produce/reconcile remain available in 2.x.


WRONG vs CORRECT Patterns

Pattern 1: Updating Nested State

// WRONG — React spread/replace pattern
setStore({ ...store, user: { ...store.user, name: "Jane" } }); // Replaces everything

// CORRECT — SolidJS path syntax
setStore("user", "name", "Jane"); // Only "name" updates

Pattern 2: Adding to Arrays

// WRONG — React immutable array pattern
setStore("items", [...store.items, newItem]); // Replaces entire array

// CORRECT — SolidJS append via index
setStore("items", store.items.length, newItem); // Adds without replacing

Pattern 3: Updating Array Items

// WRONG — React map/replace pattern
setStore("items", store.items.map(item =>
  item.id === targetId ? { ...item, done: true } : item
)); // Replaces entire array, all items re-render

// CORRECT — SolidJS filter predicate
setStore("items", (item) => item.id === targetId, "done", true); // Only matching items update

Pattern 4: Reading Store in JSX

// WRONG — Extracting value outside JSX
const name = store.user.name; // Snapshot, not reactive
return <span>{name}</span>;   // Never updates

// CORRECT — Access in JSX tracking scope
return <span>{store.user.name}</span>; // Tracked, updates reactively

Reference Links

Official Sources