.claude/skills/solidjs/solid-syntax/solid-syntax-signals/SKILL.md
Use when creating signals, effects, memos, resources, or managing component lifecycle in SolidJS. Prevents React useState/useEffect anti-patterns that break fine-grained reactivity tracking. Covers createSignal, createEffect, createMemo, createResource, createRenderEffect, createComputed, batch, untrack, on, onMount, onCleanup, observable, and from. Keywords: createSignal, createEffect, createMemo, createResource, batch, untrack, onMount, onCleanup, SolidJS reactivity, signals.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer solid-syntax-signalsInstall 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.
| Primitive | Import | Purpose | Returns |
|-----------|--------|---------|---------|
| createSignal<T> | solid-js | Reactive state atom | [Accessor<T>, Setter<T>] |
| createEffect | solid-js | Side effect on dependency change | void |
| createMemo<T> | solid-js | Cached derived value (IS a reactive source) | Accessor<T> |
| createResource<T> | solid-js | Async data with loading/error states | [Resource<T>, { mutate, refetch }] |
| createRenderEffect | solid-js | Synchronous effect during render phase | void |
| createComputed | solid-js | Before-render state synchronization | void |
| batch | solid-js | Defer propagation until callback completes | T (return value) |
| untrack | solid-js | Prevent dependency tracking | T (return value) |
| on | solid-js | Explicit dependency specification for effects | EffectFunction |
| onMount | solid-js | Run once after DOM mount (non-tracking) | void |
| onCleanup | solid-js | Dispose resources on scope cleanup (LIFO) | void |
| observable | solid-js | Convert signal to RxJS Observable | Observable<T> |
| from | solid-js | Bridge external reactive source to signal | Accessor<T> |
ALWAYS call signal getters as functions: count(), NEVER count. Forgetting the parentheses reads the getter function itself, not the value, and breaks reactivity tracking.
NEVER use dependency arrays. SolidJS tracks dependencies automatically. Writing createEffect(() => { ... }, [dep]) does NOT work like React's useEffect.
NEVER destructure props or store objects. Destructuring reads values once and kills reactive tracking. ALWAYS access properties on the original object.
ALWAYS remember: the component function runs ONCE. Only reactive expressions (effects, memos, JSX bindings) re-execute. Top-level code is setup code, not render code.
NEVER return cleanup functions from effects. SolidJS uses onCleanup() as a separate call, not a return value like React's useEffect.
NEVER use createEffect for derived values. ALWAYS use createMemo instead. Effects are for side effects only.
Need reactive state?
YES --> Simple value? --> createSignal
Complex/nested object? --> createStore (see solid-syntax-stores)
Need derived value?
YES --> Synchronous? --> createMemo
Asynchronous? --> createResource
Need side effect?
YES --> After render, DOM available? --> createEffect
During render, synchronous? --> createRenderEffect
Before render, state sync? --> createComputed
Need lifecycle hook?
YES --> Run once on mount? --> onMount
Cleanup on disposal? --> onCleanup
Need to control tracking?
YES --> Prevent tracking? --> untrack
Explicit deps only? --> on
Defer propagation? --> batch
Need external interop?
YES --> Signal to Observable? --> observable
Observable to Signal? --> from
| Aspect | createComputed | createRenderEffect | createEffect | |--------|----------------|-------------------|--------------| | When | Before render | During render (synchronous) | After render completes | | Initial run | Before DOM exists | Before DOM mount | After DOM mount | | Refs available | No | No (initial run) | Yes | | Re-runs | Before render cycle | After memos, before paint | After render + memos | | SSR behavior | Runs | Runs once (synchronous phase) | NEVER runs | | Use case | State synchronization | DOM measurements | Side effects, subscriptions |
The foundational reactive atom. Returns a getter function and a setter function.
const [count, setCount] = createSignal<number>(0);
// Read: ALWAYS call as function
count(); // 0
// Write: direct value
setCount(5);
// Write: functional update (receives previous)
setCount((prev) => prev + 1);
// Options: custom equality, debug name
const [data, setData] = createSignal<Data>(initial, {
equals: (prev, next) => prev.id === next.id,
name: "userData",
});
// Always-notify (skip equality check)
const [tick, setTick] = createSignal(0, { equals: false });
WRONG (React) vs CORRECT (SolidJS):
// WRONG -- React useState returns a value, not a getter
const [count, setCount] = useState(0);
return <div>{count}</div>; // React re-renders entire component
// CORRECT -- SolidJS createSignal returns a getter function
const [count, setCount] = createSignal(0);
return <div>{count()}</div>; // Only this text node updates
Runs side effects when tracked dependencies change. Dependencies are tracked automatically.
createEffect(() => {
document.title = `Count: ${count()}`; // Auto-tracks count
});
// With previous value
createEffect((prev: number) => {
const current = count();
console.log("Changed from", prev, "to", current);
return current;
}, 0);
// Cleanup inside effects
createEffect(() => {
const handler = () => console.log(count());
window.addEventListener("resize", handler);
onCleanup(() => window.removeEventListener("resize", handler));
});
WRONG (React) vs CORRECT (SolidJS):
// WRONG -- React: manual dependency array
useEffect(() => { console.log(count); }, [count]);
// CORRECT -- SolidJS: automatic tracking, no array
createEffect(() => { console.log(count()); });
// WRONG -- React: return cleanup function
useEffect(() => { return () => cleanup(); }, []);
// CORRECT -- SolidJS: onCleanup is a separate call
createEffect(() => { onCleanup(() => cleanup()); });
Cached derived value that IS a reactive source. Unlike React's useMemo, other computations can track a memo.
const double = createMemo(() => count() * 2);
// double() is cached, recalculates only when count() changes
// Memos ARE trackable reactive sources
createEffect(() => console.log(double())); // Tracks double
const filtered = createMemo(() =>
items().filter((item) => item.active)
);
WRONG (React) vs CORRECT (SolidJS):
// WRONG -- Using effect + signal for derived values
createEffect(() => { setDouble(count() * 2); }); // Unnecessary
// CORRECT -- Use createMemo for derived values
const double = createMemo(() => count() * 2);
Async data fetching with built-in loading/error states and Suspense integration.
// Basic fetch
const [data] = createResource(async () => {
const res = await fetch("/api/data");
return res.json();
});
// Source-based: auto-refetches when userId changes
const [userId, setUserId] = createSignal(1);
const [user] = createResource(userId, async (id) => {
const res = await fetch(`/api/users/${id}`);
return res.json();
});
// With actions
const [posts, { refetch, mutate }] = createResource(fetchPosts);
mutate((prev) => [...prev, newPost]); // Optimistic update
await refetch(); // Manual refetch
// In JSX with Suspense
<Suspense fallback={<Spinner />}>
<UserProfile />
</Suspense>
Resource states: data.state is one of "unresolved" | "pending" | "ready" | "refreshing" | "errored". Use data.loading, data.error, and data.latest for UI rendering.
Defers all downstream updates until the callback completes. Reduces m x n updates to m updates.
batch(() => {
setFirstName("John");
setLastName("Doe");
setAge(30);
}); // All downstream effects fire ONCE here
ALWAYS remember: async breaks batching. Only updates before the first await are batched. Effects and store setters auto-batch internally.
Reads a signal without creating a dependency.
createEffect(() => {
// count() IS tracked, name() is NOT
console.log(count(), untrack(() => name()));
});
Explicitly specifies which dependencies to track. Use defer: true to skip the initial run.
createEffect(on(userId, (id, prevId) => {
console.log("User changed from", prevId, "to", id);
}, { defer: true }));
// Multiple explicit deps
createEffect(on([a, b], ([aVal, bVal]) => {
console.log(aVal, bVal);
}));
function Timer() {
let ref: HTMLDivElement;
onMount(() => {
// DOM is available, refs are set
ref.focus();
});
const timer = setInterval(() => tick(), 1000);
onCleanup(() => clearInterval(timer)); // LIFO order
return <div ref={ref}>...</div>;
}
| React Pattern | Why It Breaks SolidJS | SolidJS Equivalent |
|--------------|----------------------|-------------------|
| const { name } = props | Destructures once, kills tracking | props.name (access on object) |
| const val = signal() at top level | Snapshot, never updates | Call signal() where needed |
| useEffect(() => {}, [deps]) | No dependency arrays in SolidJS | createEffect(() => { }) |
| useMemo(() => x, [deps]) | Not a reactive source | createMemo(() => x) |
| useEffect(() => { return cleanup }) | Return value ignored | onCleanup(() => cleanup()) |
| useEffect(() => {}, []) for mount | Wrong semantics | onMount(() => { }) |
| Early return before signal access | Skipped signals lose tracking | Access all signals first, then branch |
| setState({...state, key: val}) | Full replacement, not granular | setStore("key", val) path syntax |
| Feature | 1.x | 2.x |
|---------|-----|-----|
| Reactivity | Synchronous | Microtask-batched (use flush() for immediate) |
| Effects | createEffect | Split compute/apply pattern |
| Mount hook | onMount | onSettled (can return cleanup) |
| Derived state | createMemo only | createSignal(fn) for derived-but-writable |
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.