Files
nixos-config/profiles/opencode/skill/solidjs/references/api_reference.md
Christoph Schmatzler 29a2dfc606 solidjs
Signed-off-by: Christoph Schmatzler <christoph@schmatzler.com>
2026-02-05 17:08:21 +00:00

13 KiB

SolidJS API Reference

Complete reference for all SolidJS primitives, utilities, and component APIs.

Basic Reactivity

createSignal

import { createSignal } from "solid-js";

const [getter, setter] = createSignal<T>(initialValue, options?);

// Options
interface SignalOptions<T> {
  equals?: false | ((prev: T, next: T) => boolean);
  name?: string;
  internal?: boolean;
}

Examples:

const [count, setCount] = createSignal(0);
const [user, setUser] = createSignal<User | null>(null);

// Always update
const [data, setData] = createSignal(obj, { equals: false });

// Custom equality
const [items, setItems] = createSignal([], {
  equals: (a, b) => a.length === b.length
});

// Setter forms
setCount(5);                      // Direct value
setCount(prev => prev + 1);       // Functional update

createEffect

import { createEffect } from "solid-js";

createEffect<T>(fn: (prev: T) => T, initialValue?: T, options?);

// Options
interface EffectOptions {
  name?: string;
}

Examples:

// Basic
createEffect(() => {
  console.log("Count:", count());
});

// With previous value
createEffect((prev) => {
  console.log("Changed from", prev, "to", count());
  return count();
}, count());

// With cleanup
createEffect(() => {
  const handler = () => {};
  window.addEventListener("resize", handler);
  onCleanup(() => window.removeEventListener("resize", handler));
});

createMemo

import { createMemo } from "solid-js";

const getter = createMemo<T>(fn: (prev: T) => T, initialValue?: T, options?);

// Options
interface MemoOptions<T> {
  equals?: false | ((prev: T, next: T) => boolean);
  name?: string;
}

Examples:

const doubled = createMemo(() => count() * 2);
const filtered = createMemo(() => items().filter(i => i.active));

// Previous value
const delta = createMemo((prev) => count() - prev, 0);

createResource

import { createResource } from "solid-js";

const [resource, { mutate, refetch }] = createResource(
  source?,      // Optional reactive source
  fetcher,      // (source, info) => Promise<T>
  options?
);

// Resource properties
resource()           // T | undefined
resource.loading     // boolean
resource.error       // any
resource.state       // "unresolved" | "pending" | "ready" | "refreshing" | "errored"
resource.latest      // T | undefined (last successful value)

// Options
interface ResourceOptions<T> {
  initialValue?: T;
  name?: string;
  deferStream?: boolean;
  ssrLoadFrom?: "initial" | "server";
  storage?: (init: T) => [Accessor<T>, Setter<T>];
  onHydrated?: (key, info: { value: T }) => void;
}

Examples:

// Without source
const [users] = createResource(fetchUsers);

// With source
const [user] = createResource(userId, fetchUser);

// With options
const [data] = createResource(id, fetchData, {
  initialValue: [],
  deferStream: true
});

// Actions
mutate(newValue);           // Update locally
refetch();                  // Re-fetch
refetch(customInfo);        // Pass to fetcher's info.refetching

Stores

createStore

import { createStore } from "solid-js/store";

const [store, setStore] = createStore<T>(initialValue);

Update patterns:

const [state, setState] = createStore({
  user: { name: "John", age: 30 },
  todos: [{ id: 1, text: "Learn Solid", done: false }]
});

// Path syntax
setState("user", "name", "Jane");
setState("user", "age", a => a + 1);
setState("todos", 0, "done", true);

// Array operations
setState("todos", t => [...t, newTodo]);
setState("todos", todos.length, newTodo);

// Multiple paths
setState("todos", { from: 0, to: 2 }, "done", true);
setState("todos", [0, 2, 4], "done", true);
setState("todos", i => i.done, "done", false);

// Object merge (shallow)
setState("user", { age: 31 }); // Keeps other properties

produce

import { produce } from "solid-js/store";

setState(produce(draft => {
  draft.user.age++;
  draft.todos.push({ id: 2, text: "New", done: false });
  draft.todos[0].done = true;
}));

reconcile

import { reconcile } from "solid-js/store";

// Replace with diff (minimal updates)
setState("todos", reconcile(newTodosFromAPI));

// Options
reconcile(data, { key: "id", merge: true });

unwrap

import { unwrap } from "solid-js/store";

const raw = unwrap(store);  // Non-reactive plain object

createMutable

import { createMutable } from "solid-js/store";

const state = createMutable({
  count: 0,
  user: { name: "John" }
});

// Direct mutation (like MobX)
state.count++;
state.user.name = "Jane";

modifyMutable

import { modifyMutable, reconcile, produce } from "solid-js/store";

modifyMutable(state, reconcile(newData));
modifyMutable(state, produce(s => { s.count++ }));

Component APIs

children

import { children } from "solid-js";

const resolved = children(() => props.children);

// Access
resolved();           // JSX.Element | JSX.Element[]
resolved.toArray();   // Always array

createContext / useContext

import { createContext, useContext } from "solid-js";

const MyContext = createContext<T>(defaultValue?);

// Provider
<MyContext.Provider value={value}>
  {children}
</MyContext.Provider>

// Consumer
const value = useContext(MyContext);

createUniqueId

import { createUniqueId } from "solid-js";

const id = createUniqueId();  // "0", "1", etc.

lazy

import { lazy } from "solid-js";

const LazyComponent = lazy(() => import("./Component"));

// Use with Suspense
<Suspense fallback={<Loading />}>
  <LazyComponent />
</Suspense>

Lifecycle

onMount

import { onMount } from "solid-js";

onMount(() => {
  // Runs once after initial render
  console.log("Mounted");
});

onCleanup

import { onCleanup } from "solid-js";

// In component
onCleanup(() => {
  console.log("Cleaning up");
});

// In effect
createEffect(() => {
  const sub = subscribe();
  onCleanup(() => sub.unsubscribe());
});

Reactive Utilities

batch

import { batch } from "solid-js";

batch(() => {
  setA(1);
  setB(2);
  setC(3);
  // Effects run once after batch
});

untrack

import { untrack } from "solid-js";

createEffect(() => {
  console.log(a());              // Tracked
  console.log(untrack(() => b())); // Not tracked
});

on

import { on } from "solid-js";

// Explicit dependencies
createEffect(on(count, (value, prev) => {
  console.log("Count changed:", prev, "->", value);
}));

// Multiple dependencies
createEffect(on([a, b], ([a, b], [prevA, prevB]) => {
  console.log("Changed");
}));

// Defer first run
createEffect(on(count, (v) => console.log(v), { defer: true }));

mergeProps

import { mergeProps } from "solid-js";

const merged = mergeProps(
  { size: "medium", color: "blue" },  // Defaults
  props                                 // Overrides
);

splitProps

import { splitProps } from "solid-js";

const [local, others] = splitProps(props, ["class", "onClick"]);
// local.class, local.onClick
// others contains everything else

const [a, b, rest] = splitProps(props, ["foo"], ["bar"]);

createRoot

import { createRoot } from "solid-js";

const dispose = createRoot((dispose) => {
  const [count, setCount] = createSignal(0);
  // Use signals...
  return dispose;
});

// Later
dispose();

getOwner / runWithOwner

import { getOwner, runWithOwner } from "solid-js";

const owner = getOwner();

// Later, in async code
runWithOwner(owner, () => {
  createEffect(() => {
    // This effect has proper ownership
  });
});

mapArray

import { mapArray } from "solid-js";

const mapped = mapArray(
  () => items(),
  (item, index) => ({ ...item, doubled: item.value * 2 })
);

indexArray

import { indexArray } from "solid-js";

const mapped = indexArray(
  () => items(),
  (item, index) => <div>{index}: {item().name}</div>
);

observable

import { observable } from "solid-js";

const obs = observable(signal);
obs.subscribe((value) => console.log(value));

from

import { from } from "solid-js";

// Convert observable/subscribable to signal
const signal = from(rxObservable);
const signal = from((set) => {
  const unsub = subscribe(set);
  return unsub;
});

catchError

import { catchError } from "solid-js";

catchError(
  () => riskyOperation(),
  (err) => console.error("Error:", err)
);

Secondary Primitives

createComputed

import { createComputed } from "solid-js";

// Like createEffect but runs during render phase
createComputed(() => {
  setDerived(source() * 2);
});

createRenderEffect

import { createRenderEffect } from "solid-js";

// Runs before paint (for DOM measurements)
createRenderEffect(() => {
  const height = element.offsetHeight;
});

createDeferred

import { createDeferred } from "solid-js";

// Returns value after idle time
const deferred = createDeferred(() => expensiveComputation(), {
  timeoutMs: 1000
});

createReaction

import { createReaction } from "solid-js";

const track = createReaction(() => {
  console.log("Something changed");
});

track(() => count()); // Start tracking

createSelector

import { createSelector } from "solid-js";

const isSelected = createSelector(selectedId);

<For each={items()}>
  {(item) => (
    <div class={isSelected(item.id) ? "selected" : ""}>
      {item.name}
    </div>
  )}
</For>

Components

Show

<Show when={condition()} fallback={<Fallback />}>
  <Content />
</Show>

// With callback (narrowed type)
<Show when={user()}>
  {(user) => <div>{user().name}</div>}
</Show>

For

<For each={items()} fallback={<Empty />}>
  {(item, index) => <div>{index()}: {item.name}</div>}
</For>

Index

<Index each={items()} fallback={<Empty />}>
  {(item, index) => <input value={item().text} />}
</Index>

Switch / Match

<Switch fallback={<Default />}>
  <Match when={state() === "loading"}>
    <Loading />
  </Match>
  <Match when={state() === "error"}>
    <Error />
  </Match>
</Switch>

Dynamic

import { Dynamic } from "solid-js/web";

<Dynamic component={selected()} prop={value} />
<Dynamic component="div" class="dynamic">Content</Dynamic>

Portal

import { Portal } from "solid-js/web";

<Portal mount={document.body}>
  <Modal />
</Portal>

ErrorBoundary

<ErrorBoundary fallback={(err, reset) => (
  <div>
    <p>Error: {err.message}</p>
    <button onClick={reset}>Retry</button>
  </div>
)}>
  <Content />
</ErrorBoundary>

Suspense

<Suspense fallback={<Loading />}>
  <AsyncContent />
</Suspense>

SuspenseList

<SuspenseList revealOrder="forwards" tail="collapsed">
  <Suspense fallback={<Loading />}><Item1 /></Suspense>
  <Suspense fallback={<Loading />}><Item2 /></Suspense>
  <Suspense fallback={<Loading />}><Item3 /></Suspense>
</SuspenseList>

Rendering

render

import { render } from "solid-js/web";

const dispose = render(() => <App />, document.getElementById("root")!);

// Cleanup
dispose();

hydrate

import { hydrate } from "solid-js/web";

hydrate(() => <App />, document.getElementById("root")!);

renderToString

import { renderToString } from "solid-js/web";

const html = renderToString(() => <App />);

renderToStringAsync

import { renderToStringAsync } from "solid-js/web";

const html = await renderToStringAsync(() => <App />);

renderToStream

import { renderToStream } from "solid-js/web";

const stream = renderToStream(() => <App />);
stream.pipe(res);

isServer

import { isServer } from "solid-js/web";

if (isServer) {
  // Server-only code
}

JSX Attributes

ref

let el: HTMLDivElement;
<div ref={el} />
<div ref={(e) => console.log(e)} />

classList

<div classList={{ active: isActive(), disabled: isDisabled() }} />

style

<div style={{ color: "red", "font-size": "14px" }} />
<div style={`color: ${color()}`} />

on:event (native)

<div on:click={handleClick} />
<div on:scroll={handleScroll} />

use:directive

function clickOutside(el: HTMLElement, accessor: () => () => void) {
  const handler = (e: MouseEvent) => {
    if (!el.contains(e.target as Node)) accessor()();
  };
  document.addEventListener("click", handler);
  onCleanup(() => document.removeEventListener("click", handler));
}

<div use:clickOutside={() => setOpen(false)} />

prop:property

<input prop:value={value()} />  // Set as property, not attribute

attr:attribute

<div attr:data-custom={value()} />  // Force attribute

bool:attribute

<input bool:disabled={isDisabled()} />

@once

<div title={/*@once*/ staticValue} />  // Never updates

Types

import type {
  Component,
  ParentComponent,
  FlowComponent,
  VoidComponent,
  JSX,
  Accessor,
  Setter,
  Signal,
  Resource,
  Owner
} from "solid-js";

// Component types
const MyComponent: Component<Props> = (props) => <div />;
const Parent: ParentComponent<Props> = (props) => <div>{props.children}</div>;
const Flow: FlowComponent<Props, Item> = (props) => props.children(item);
const Void: VoidComponent<Props> = (props) => <input />;

// Event types
type Handler = JSX.EventHandler<HTMLButtonElement, MouseEvent>;
type ChangeHandler = JSX.ChangeEventHandler<HTMLInputElement>;