.claude/skills/solidjs/solid-syntax/solid-syntax-stores/SKILL.md
Use when managing complex nested state, updating objects or arrays, or choosing between signals and stores in SolidJS. Prevents direct state mutation and spread-copy patterns that destroy store proxy reactivity. Covers createStore, setStore path syntax, produce, reconcile, unwrap, and createMutable. Keywords: createStore, setStore, produce, reconcile, unwrap, createMutable, SolidJS store, nested state, proxy reactivity.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer solid-syntax-storesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
// ALL store utilities come from "solid-js/store" — NEVER from "solid-js"
import { createStore, produce, reconcile, unwrap, createMutable } from "solid-js/store";
| 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 |
| 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.
| 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 |
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);
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,
});
The setter uses a path-based syntax for surgical nested updates. See references/methods.md for complete API signatures.
| 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) |
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"
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;
}));
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.
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
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.
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>;
| 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 — React spread/replace pattern
setStore({ ...store, user: { ...store.user, name: "Jane" } }); // Replaces everything
// CORRECT — SolidJS path syntax
setStore("user", "name", "Jane"); // Only "name" updates
// 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
// 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
// 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
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.