SolidJS Development
SolidJS is a declarative JavaScript library for building user interfaces with fine-grained reactivity. Unlike virtual DOM frameworks, Solid compiles templates to real DOM nodes and updates them with fine-grained reactions.
Core Principles Components run once — Component functions execute only during initialization, not on every update Fine-grained reactivity — Only the specific DOM nodes that depend on changed data update No virtual DOM — Direct DOM manipulation via compiled templates Signals are functions — Access values by calling: count() not count Reactivity Primitives Signals — Basic State import { createSignal } from "solid-js";
const [count, setCount] = createSignal(0);
// Read value (getter) console.log(count()); // 0
// Update value (setter) setCount(1); setCount(prev => prev + 1); // Functional update
Options:
const [value, setValue] = createSignal(initialValue, { equals: false, // Always trigger updates, even if value unchanged name: "debugName" // For devtools });
Effects — Side Effects import { createEffect } from "solid-js";
createEffect(() => { console.log("Count changed:", count()); // Runs after render, re-runs when dependencies change });
Key behaviors:
Initial run: after render, before browser paint Subsequent runs: when tracked dependencies change Never runs during SSR or hydration Use onCleanup for cleanup logic Memos — Derived/Cached Values import { createMemo } from "solid-js";
const doubled = createMemo(() => count() * 2);
// Access like signal console.log(doubled()); // Cached, only recalculates when count changes
Use memos when:
Derived value is expensive to compute Derived value is accessed multiple times You want to prevent downstream updates when result unchanged Resources — Async Data import { createResource } from "solid-js";
const [user, { mutate, refetch }] = createResource(userId, fetchUser);
// In JSX
// Resource properties user.loading // boolean user.error // error if failed user.state // "unresolved" | "pending" | "ready" | "refreshing" | "errored" user.latest // last successful value
Stores — Complex State
For nested objects/arrays with fine-grained updates:
import { createStore } from "solid-js/store";
const [state, setState] = createStore({ user: { name: "John", age: 30 }, todos: [] });
// Path syntax updates setState("user", "name", "Jane"); setState("todos", todos => [...todos, newTodo]); setState("todos", 0, "completed", true);
// Produce for immer-like updates import { produce } from "solid-js/store"; setState(produce(s => { s.user.age++; s.todos.push(newTodo); }));
Store utilities:
produce — Immer-like mutations reconcile — Diff and patch data (for API responses) unwrap — Get raw non-reactive object Components Basic Component import { Component } from "solid-js";
const MyComponent: Component<{ name: string }> = (props) => { return
Props Handling import { splitProps, mergeProps } from "solid-js";
// Default props const merged = mergeProps({ size: "medium" }, props);
// Split props (for spreading) const [local, others] = splitProps(props, ["class", "onClick"]); return ;
Props rules:
Props are reactive getters — don't destructure at top level Use props.value in JSX, not const { value } = props Children Helper import { children } from "solid-js";
const Wrapper: Component = (props) => { const resolved = children(() => props.children);
createEffect(() => { console.log("Children:", resolved()); });
return
Control Flow Components Show — Conditional Rendering import { Show } from "solid-js";
For — List Rendering (keyed by reference) import { For } from "solid-js";
Note: index is a signal, item is the value.
Index — List Rendering (keyed by index) import { Index } from "solid-js";
Note: item is a signal, index is the value. Better for primitive arrays or inputs.
Switch/Match — Multiple Conditions import { Switch, Match } from "solid-js";
Dynamic — Dynamic Component import { Dynamic } from "solid-js/web";
Portal — Render Outside DOM Hierarchy import { Portal } from "solid-js/web";
ErrorBoundary — Error Handling import { ErrorBoundary } from "solid-js";
)}>
Suspense — Async Loading import { Suspense } from "solid-js";
Context API import { createContext, useContext } from "solid-js";
// Create context const CounterContext = createContext<{ count: () => number; increment: () => void; }>();
// Provider component export function CounterProvider(props) { const [count, setCount] = createSignal(0);
return (
// Consumer hook export function useCounter() { const ctx = useContext(CounterContext); if (!ctx) throw new Error("useCounter must be used within CounterProvider"); return ctx; }
Lifecycle import { onMount, onCleanup } from "solid-js";
function MyComponent() { onMount(() => { console.log("Mounted"); const handler = () => {}; window.addEventListener("resize", handler);
onCleanup(() => {
window.removeEventListener("resize", handler);
});
});
return
Refs let inputRef: HTMLInputElement;
{ / el is the DOM element / }} />
Event Handling // Standard events (lowercase)
// Delegated events (on:)
// Native events (on:) - not delegated
Routing (Solid Router)
See references/routing.md for complete routing guide.
Quick setup:
import { Router, Route } from "@solidjs/router";
SolidStart
See references/solidstart.md for full-stack development guide.
Quick setup:
npm create solid@latest my-app cd my-app && npm install && npm run dev
Common Patterns Conditional Classes import { clsx } from "clsx"; // or classList
Batch Updates import { batch } from "solid-js";
batch(() => { setName("John"); setAge(30); // Effects run once after batch completes });
Untrack import { untrack } from "solid-js";
createEffect(() => { console.log(count()); // tracked console.log(untrack(() => other())); // not tracked });
TypeScript import type { Component, ParentComponent, JSX } from "solid-js";
// Basic component const Button: Component<{ label: string }> = (props) => ( );
// With children
const Layout: ParentComponent<{ title: string }> = (props) => (
{props.title}
{props.children});
// Event handler types
const handleClick: JSX.EventHandler
Project Setup
Create new project
npm create solid@latest my-app
With template
npx degit solidjs/templates/ts my-app
SolidStart
npm create solid@latest my-app -- --template solidstart
vite.config.ts:
import { defineConfig } from "vite"; import solid from "vite-plugin-solid";
export default defineConfig({ plugins: [solid()] });
Anti-Patterns to Avoid
Destructuring props — Breaks reactivity
// ❌ Bad const { name } = props;
// ✅ Good props.name
Accessing signals outside tracking scope
// ❌ Won't update console.log(count());
// ✅ Will update createEffect(() => console.log(count()));
Forgetting to call signal getters
// ❌ Passes the function
// ✅ Passes the value
Using array index as key — Use
Side effects during render — Use createEffect or onMount