Compile-time functions
Compile-time functions (CTFs) look like regular calls but are erased at build
time and replaced with Solid 2.0 primitives. solid-labels declares them
globally, so they need no imports, and every Solid API the output uses is
imported for you.
Every example on this page is compiled by the real compiler when the site is
built. Where the dev build differs from the prod build, a switch appears
above the output. In dev, signals and memos also get a name option.
#Reactivity
#$signal
function $signal<T>(): T | undefined;
function $signal<T>(
value: T,
options?: {
equals?: false | ((prev: T, next: T) => boolean);
name?: string;
ownedWrite?: boolean;
unobserved?: () => void;
},
): T;Declares a signal variable. Reads compile to accessor calls, writes to setter calls. Only allowed as a variable initializer.
Memoized signal convention: a static initializer (literal, identifier, static object/array) is stored as-is. A non-static initializer, or one that reads another reactive variable, is wrapped in a compute function, producing Solid 2.0's writable derived form:
let value = $signal(foo());
let count = $signal(0);
let doubled = $signal(count * 2);import { createSignal as _createSignal } from "solid-js";
let [value, setvalue_1] = _createSignal(() => foo());
let [count, setcount_1] = _createSignal(0);
let [doubled, setdoubled_1] = _createSignal(() => count() * 2);import { createSignal as _createSignal } from "solid-js";
let [value, setvalue_1] = _createSignal(() => foo(), { name: "value" });
let [count, setcount_1] = _createSignal(0, { name: "count" });
let [doubled, setdoubled_1] = _createSignal(() => count() * 2, { name: "doubled" });doubled recomputes from count until it is written, then holds the written
value, as createSignal(fn) does. Pass a function explicitly
($signal(() => expr)) to opt in regardless of the initializer's shape.
let count = $signal(0);
console.log(count);
count = 10;
count += 1;
count++;import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0);
console.log(count());
setcount_1(() => 10);
setcount_1(current_1 => current_1 += 1);
setcount_1(current_2 => ++current_2);import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
console.log(count());
setcount_1(() => 10);
setcount_1(current_1 => current_1 += 1);
setcount_1(current_2 => ++current_2);Assignments whose right side awaits or yields are split so the setter never captures the promise:
let count = $signal(0);
async function example() {
count = await asyncValue();
}import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0);
async function example() {
const tmp_1 = await asyncValue();
setcount_1(() => tmp_1);
}import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
async function example() {
const tmp_1 = await asyncValue();
setcount_1(() => tmp_1);
}#$memo
function $memo<T>(
value: T,
options?: {
equals?: false | ((prev: T, next: T) => boolean);
name?: string;
lazy?: boolean;
unobserved?: () => void;
},
): T;Declares a derived variable. Reads compile to accessor calls. Writes are a compile error.
let count = $signal(0);
const message = $memo(`Count: ${count}`);
console.log(message);import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const message = _createMemo(() => `Count: ${count()}`);
console.log(message());import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const message = _createMemo(() => `Count: ${count()}`, { name: "message" });
console.log(message());#$
Shorthand with two forms: in a variable declaration it is $memo; as a
statement the expression is compiled exactly like an $effect
body: it captures identifiers, generates a compute phase and hoists the callback.
let count = $signal(0);
const message = $(`Count: ${count}`);
$(console.log(message));import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const message = _createMemo(() => `Count: ${count()}`);
_createEffect(() => [message()], effectCallback_1);
function effectCallback_1([message]) {
console.log(message);
}import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const message = _createMemo(() => `Count: ${count()}`, { name: "message" });
_createEffect(() => [message()], effectCallback_1);
function effectCallback_1([message]) {
console.log(message);
}The expression may not await or yield, and its value is discarded (it is
emitted as a statement, so a function value is never mistaken for an effect
cleanup).
#$effect and $renderEffect
function $effect(callback: () => (() => void) | void, options?: EffectOptions): void;
function $renderEffect(callback: () => (() => void) | void, options?: EffectOptions): void;Solid 2.0 effects have two phases: a tracked compute function and an
untracked effect function. $effect compiles the familiar single-callback
form into both phases mechanically:
- The compiler collects every outer identifier the callback uses.
- The generated compute phase returns their current values. Signals and memos are read there, so that phase does the tracking.
- The callback receives the values as a destructured array parameter. Its body reads plain values and stays untracked.
function App() {
let bar = $signal(someValue());
$effect(() => {
foo(bar);
});
}import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
let [bar, setbar_1] = _createSignal(() => someValue());
_createEffect(() => [bar()], AppEffect_1);
}
function AppEffect_1([bar]) {
foo(bar);
}import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
let [bar, setbar_1] = _createSignal(() => someValue(), { name: "bar" });
_createEffect(() => [bar()], AppEffect_1);
}
function AppEffect_1([bar]) {
foo(bar);
}The callback is hoisted to module level as a named function whenever that is
safe. Its name comes from the nearest enclosing named scope, such as AppEffect_1
inside function App() (also picked up through const App = ...,
$component(...), object methods, and assignments), with effectCallback_1
as the module-level fallback. It stays inline, with the same array parameter, when the callback:
- writes to a plain (non-signal) outer variable, which must keep closure semantics, or
- uses
this,super, orarguments.
function App() {
let el;
let count = $signal(0);
$effect(() => {
el = render(count);
});
}import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
let el;
let [count, setcount_1] = _createSignal(0);
_createEffect(() => [count()], ([count]) => {
el = render(count);
});
}import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
function App() {
let el;
let [count, setcount_1] = _createSignal(0, { name: "count" });
_createEffect(() => [count()], ([count]) => {
el = render(count);
});
}Signal writes inside the callback work. The compiler captures the setter:
let count = $signal(0);
$effect(() => {
count += 1;
});import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0);
_createEffect(() => [setcount_1], effectCallback_1);
function effectCallback_1([setcount_1]) {
setcount_1(current_1 => current_1 += 1);
}import { createSignal as _createSignal } from "solid-js";
import { createEffect as _createEffect } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
_createEffect(() => [setcount_1], effectCallback_1);
function effectCallback_1([setcount_1]) {
setcount_1(current_1 => current_1 += 1);
}Rules:
- The callback takes no parameters. Captured values are injected by the compiler.
- The callback must be synchronous (no
async, no generators). - Module-scope plain values are not captured. They resolve directly.
- Reads wrapped in
$untrack(...)are excluded from the tracked source list: the signal's accessor is passed through the capture array (a stable identity that never triggers the effect) and the body performs a real untracked read,untrack(() => count()), at effect time. A signal read both inside and outside$untrackin the same callback is tracked. - A function returned from the callback is the cleanup: it runs before
the next application and on dispose, exactly as in
createEffect. (The$(expr);statement form discards the expression value. Use the callback form when you need cleanup.) - An optional second argument is passed through as
EffectOptions({ defer: true }, …).
$renderEffect compiles identically to createRenderEffect. Reach for it
only when authoring renderer internals.
let width = $signal(0);
$renderEffect(() => {
element.style.width = `${width}px`;
});import { createSignal as _createSignal } from "solid-js";
import { createRenderEffect as _createRenderEffect } from "solid-js";
let [width, setwidth_1] = _createSignal(0);
_createRenderEffect(() => [width()], effectCallback_1);
function effectCallback_1([width]) {
element.style.width = `${width}px`;
}import { createSignal as _createSignal } from "solid-js";
import { createRenderEffect as _createRenderEffect } from "solid-js";
let [width, setwidth_1] = _createSignal(0, { name: "width" });
_createRenderEffect(() => [width()], effectCallback_1);
function effectCallback_1([width]) {
element.style.width = `${width}px`;
}#$untrack
function $untrack<T>(value: T): T;Compiles to untrack, wrapping the value in an arrow function.
let count = $signal(0);
const value = $untrack(count);import { createSignal as _createSignal } from "solid-js";
import { untrack as _untrack } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const value = _untrack(() => count());import { createSignal as _createSignal } from "solid-js";
import { untrack as _untrack } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const value = _untrack(() => count());Inside an $effect callback, $untrack also
changes what the effect tracks: reads wrapped in it are excluded from the
generated source list.
Solid 2.0 batches writes by default, so there is no
$batch. Use$flushto apply queued writes synchronously.
#$root, $reaction, $flush, $onFlush
Aliases for createRoot, createReaction, flush and onFlush.
$onFlush(callback) runs the callback once after the pending flush.
let count = $signal(0);
const dispose = $root((dispose) => {
$onFlush(() => console.log('flushed'));
return dispose;
});
const track = $reaction(() => console.log('changed'));
count += 1;
$flush();import { createSignal as _createSignal } from "solid-js";
import { createRoot as _createRoot } from "solid-js";
import { onFlush as _onFlush } from "solid-js";
import { createReaction as _createReaction } from "solid-js";
import { flush as _flush } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const dispose = _createRoot((dispose) => {
_onFlush(() => console.log('flushed'));
return dispose;
});
const track = _createReaction(() => console.log('changed'));
setcount_1(current_1 => current_1 += 1);
_flush();import { createSignal as _createSignal } from "solid-js";
import { createRoot as _createRoot } from "solid-js";
import { onFlush as _onFlush } from "solid-js";
import { createReaction as _createReaction } from "solid-js";
import { flush as _flush } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const dispose = _createRoot((dispose) => {
_onFlush(() => console.log('flushed'));
return dispose;
});
const track = _createReaction(() => console.log('changed'));
setcount_1(current_1 => current_1 += 1);
_flush();#$owner, $observer, $isDisposed
Aliases for getOwner, getObserver and isDisposed. $observer() returns
the current tracking scope, or null outside one.
$isDisposed(owner) tells a late callback whether its owner is gone.
const owner = $owner();
const scope = $observer();
queueMicrotask(() => {
if (!$isDisposed(owner)) {
$runWithOwner(owner, work);
}
});import { getOwner as _getOwner } from "solid-js";
import { getObserver as _getObserver } from "solid-js";
import { isDisposed as _isDisposed } from "solid-js";
import { runWithOwner as _runWithOwner } from "solid-js";
const owner = _getOwner();
const scope = _getObserver();
queueMicrotask(() => {
if (!_isDisposed(owner)) {
_runWithOwner(owner, work);
}
});#Lifecycles
$settled→onSettled. Runs once after the current activity settles. Return a cleanup function for teardown. This replaces the 1.xonMount/onCleanuppairing for component-level lifecycle.$cleanup→onCleanup. Reactive cleanup inside computations (library/primitive internals).
$settled(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
});
$cleanup(() => console.log('the owner is disposed'));import { onSettled as _onSettled } from "solid-js";
import { onCleanup as _onCleanup } from "solid-js";
_onSettled(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
});
_onCleanup(() => console.log('the owner is disposed'));#Async
Compile-time aliases for the Solid 2.0 async surface:
$action→action$isPending→isPending$latest→latest$refresh→refresh$affects→affects$flush→flush$resolve→resolve(the value is wrapped in an arrow function)$until→until(the predicate is wrapped in an arrow function)$from→from. Turns a subscription, an Observable or an async iterable into an async iterable for a computation
const save = $action(function* (todo) {
yield api.add(todo);
$refresh(todos);
});
const settled = await $resolve(user);
await $until(user, { timeout: 5000 });
const fresh = $latest(() => user);
$affects(save);
const position = $memo(() =>
$from((next, error) => {
const id = navigator.geolocation.watchPosition(next, error);
return () => navigator.geolocation.clearWatch(id);
}),
);import { action as _action } from "solid-js";
import { refresh as _refresh } from "solid-js";
import { resolve as _resolve } from "solid-js";
import { until as _until } from "solid-js";
import { latest as _latest } from "solid-js";
import { affects as _affects } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { from as _from } from "solid-js";
const save = _action(function* (todo) {
yield api.add(todo);
_refresh(todos);
});
const settled = await _resolve(() => user);
await _until(() => user, { timeout: 5000 });
const fresh = _latest(() => user);
_affects(save);
const position = _createMemo(() =>
_from((next, error) => {
const id = navigator.geolocation.watchPosition(next, error);
return () => navigator.geolocation.clearWatch(id);
}),
);import { action as _action } from "solid-js";
import { refresh as _refresh } from "solid-js";
import { resolve as _resolve } from "solid-js";
import { until as _until } from "solid-js";
import { latest as _latest } from "solid-js";
import { affects as _affects } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
import { from as _from } from "solid-js";
const save = _action(function* (todo) {
yield api.add(todo);
_refresh(todos);
});
const settled = await _resolve(() => user);
await _until(() => user, { timeout: 5000 });
const fresh = _latest(() => user);
_affects(save);
const position = _createMemo(() =>
_from((next, error) => {
const id = navigator.geolocation.watchPosition(next, error);
return () => navigator.geolocation.clearWatch(id);
}), { name: "position" },
);#$deferred
Declaration CTF for createDeferred. The variable becomes an accessor under
its original name, like $memo. After its first answer, a refetch keeps the
previous value until the new one lands, and $isPending reports the refetch.
The value is wrapped in an arrow function.
In 1.x, $deferred lagged a sync signal and took timeoutMs. In 2.0 it wraps
an async computation, and timeoutMs is gone.
let period = $signal('week');
const rows = $deferred(fetchRows(period));
const stale = $isPending(() => rows);import { createSignal as _createSignal } from "solid-js";
import { createDeferred as _createDeferred } from "solid-js";
import { isPending as _isPending } from "solid-js";
let [period, setperiod_1] = _createSignal('week');
const rows = _createDeferred(() => fetchRows(period()));
const stale = _isPending(() => rows());import { createSignal as _createSignal } from "solid-js";
import { createDeferred as _createDeferred } from "solid-js";
import { isPending as _isPending } from "solid-js";
let [period, setperiod_1] = _createSignal('week', { name: "period" });
const rows = _createDeferred(() => fetchRows(period()), { name: "rows" });
const stale = _isPending(() => rows());#Objects
#$destructure
function $destructure<T>(value: T): T;Destructures an object or array while retaining reactivity: every leaf
becomes an accessor under its original name. Rest elements compile to omit.
Supports nesting, defaults, and computed keys.
let {
a: { b, c },
b: { d = defaultD, e = compute() },
...f
} = $destructure(x);
console.log(b, c, d, e, f);import { createMemo as _createMemo } from "solid-js";
import { omit as _omit } from "solid-js";
let prop_1 = () => x.a,
b = () => prop_1().b,
c = () => prop_1().c,
prop_2 = () => x.b,
def_1 = defaultD,
d = () => { const value_1 = prop_2().d; return value_1 === void 0 ? def_1 : value_1; },
def_2 = _createMemo(() => compute()),
e = () => { const value_2 = prop_2().e; return value_2 === void 0 ? def_2() : value_2; },
f = _omit(x, "a", "b");
console.log(b(), c(), d(), e(), f);If the source is itself reactive, every property access re-reads it:
let state = $signal({ a: 1 });
const { a } = $destructure(state);import { createSignal as _createSignal } from "solid-js";
let [state, setstate_1] = _createSignal({ a: 1 });
const a = () => state().a;import { createSignal as _createSignal } from "solid-js";
let [state, setstate_1] = _createSignal({ a: 1 }, { name: "state" });
const a = () => state().a;#$component
Wraps a component function and turns an object-pattern parameter into a reactive destructure. Props stay live even though they look destructured.
const App = $component(({ name, count = 0 }) => {
return (
<div>
{name}: {count}
</div>
);
});const App = (props_1) => {
const name = () => props_1.name,
def_1 = 0,
count = () => { const value_1 = props_1.count; return value_1 === void 0 ? def_1 : value_1; };
return (
<div>
{name()}: {count()}
</div>
);
};#$merge and $omit
Aliases for merge (formerly mergeProps) and omit (formerly
splitProps).
const merged = $merge(defaults, props);
const rest = $omit(props, 'class', 'style');import { merge as _merge } from "solid-js";
import { omit as _omit } from "solid-js";
const merged = _merge(defaults, props);
const rest = _omit(props, 'class', 'style');#$getter, $setter and $property
Expose a reactive variable as a live object property. Only valid as object property values.
let count = $signal(0);
const a = { value: $getter(count) };
const b = { value: $setter(count) };
const c = { value: $property(count) };import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const a = { get value() { return count(); } };
const b = { set value(param_1) { setcount_1(() => param_1); } };
const c = { get value() { return count(); }, set value(param_2) { setcount_1(() => param_2); } };import { createSignal as _createSignal } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const a = { get value() { return count(); } };
const b = { set value(param_1) { setcount_1(() => param_1); } };
const c = { get value() { return count(); }, set value(param_2) { setcount_1(() => param_2); } };#Arrays
#$mapArray
function $mapArray<T, U>(
arr: readonly T[] | undefined | null | false,
mapFn: (v: T, i: Accessor<number>) => U,
options?: { keyed?: true; fallback?: Accessor<any>; name?: string },
): U[];
function $mapArray<T, U>(
arr: readonly T[] | undefined | null | false,
mapFn: (v: Accessor<T>, i: number) => U,
options: { keyed: false; fallback?: Accessor<any>; name?: string },
): U[];Compiles to mapArray; the result is an accessor variable. Non-keyed mapping
uses { keyed: false }. indexArray is gone in 2.0.
let list = $signal([]);
const squares = $mapArray(list, (value) => value ** 2);import { createSignal as _createSignal } from "solid-js";
import { mapArray as _mapArray } from "solid-js";
let [list, setlist_1] = _createSignal([]);
const squares = _mapArray(() => list(), (value) => value ** 2);import { createSignal as _createSignal } from "solid-js";
import { mapArray as _mapArray } from "solid-js";
let [list, setlist_1] = _createSignal([], { name: "list" });
const squares = _mapArray(() => list(), (value) => value ** 2);#$repeat
function $repeat<U>(
count: number,
mapFn: (i: number) => U,
options?: { from?: number; fallback?: Accessor<any>; name?: string },
): U[];Compiles to repeat. Count-driven rendering, the underlying helper of
<Repeat>.
let total = $signal(10);
const rows = $repeat(total, (index) => index * 2);import { createSignal as _createSignal } from "solid-js";
import { repeat as _repeat } from "solid-js";
let [total, settotal_1] = _createSignal(10);
const rows = _repeat(() => total(), (index) => index * 2);import { createSignal as _createSignal } from "solid-js";
import { repeat as _repeat } from "solid-js";
let [total, settotal_1] = _createSignal(10, { name: "total" });
const rows = _repeat(() => total(), (index) => index * 2);#Component APIs
#$uid
Alias for createUniqueId.
const id = $uid();import { createUniqueId as _createUniqueId } from "solid-js";
const id = _createUniqueId();#$lazy
function $lazy<T extends Component<any>>(
fn: Promise<{ default: T }>,
): T & {
preload: () => void;
};const Page = $lazy(import('./Page'));import { lazy as _lazy } from "solid-js";
const Page = _lazy(() => import('./Page'));#$lazyModule
Wraps a dynamic import for a module that is not a component, like $lazy
does for a component. The promise is wrapped in an arrow function. The result
is callable and has preload() and peek().
const admin = $lazyModule(import('./admin/routes'));import { lazyModule as _lazyModule } from "solid-js";
const admin = _lazyModule(() => import('./admin/routes'));#$children
Resolves children into an accessor variable.
const nodes = $children(props.children);
console.log(nodes);import { children as _children } from "solid-js";
const nodes = _children(() => props.children);
console.log(nodes());#Context
const CounterContext = $createContext(0);
const value = $useContext(CounterContext);import { createContext as _createContext } from "solid-js";
import { useContext as _useContext } from "solid-js";
const CounterContext = _createContext(0);
const value = _useContext(CounterContext);Compiles to createContext / useContext. In 2.0 the context value is the
provider: <CounterContext value={10}>...</CounterContext>.
#Owner
$owner → getOwner, $runWithOwner → runWithOwner.
#Refs and Derefs
Opt in and out of the variable syntax. Use these to return real signal tuples or to adopt them from third-party code.
function $refSignal<T>(value: T): [Accessor<T>, Setter<T>];
function $refMemo<T>(value: T): Accessor<T>;
function $get<T>(value: T): Accessor<T>;
function $set<T>(value: T): Setter<T>;
function $derefSignal<T>(value: [Accessor<T>, Setter<T>]): T;
function $derefMemo<T>(value: Accessor<T>): T;let count = $signal(0);
const tuple = $refSignal(count);
const read = $get(count);
const write = $set(count);
let adopted = $derefSignal(useCounter());
adopted += 1;
const double = $memo(count * 2);
const accessor = $refMemo(double);
const external = $derefMemo(useDouble());
console.log(external);import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0);
const tuple = [count, setcount_1];
const read = count;
const write = setcount_1;
let [adopted, setadopted_1] = useCounter();
setadopted_1(current_1 => current_1 += 1);
const double = _createMemo(() => count() * 2);
const accessor = double;
const external = useDouble();
console.log(external());import { createSignal as _createSignal } from "solid-js";
import { createMemo as _createMemo } from "solid-js";
let [count, setcount_1] = _createSignal(0, { name: "count" });
const tuple = [count, setcount_1];
const read = count;
const write = setcount_1;
let [adopted, setadopted_1] = useCounter();
setadopted_1(current_1 => current_1 += 1);
const double = _createMemo(() => count() * 2, { name: "double" });
const accessor = double;
const external = useDouble();
console.log(external());#Stores
Store APIs live in solid-js on Solid 2.0:
$store→createStore$projection→createProjection$optimistic→createOptimistic$optimisticStore→createOptimisticStore$reconcile→reconcile$snapshot→snapshot$deep→deep$isWrappable→isWrappable
const [state, setState] = $store({ user: { name: 'Ada' }, items: [] });
const [view] = $projection((draft) => {
draft.count = state.items.length;
}, { count: 0 });
const [likes, setLikes] = $optimistic(0);
const [todos, setTodos] = $optimisticStore({ list: [] });
setState((draft) => {
draft.user = $reconcile(next.user);
});
console.log($snapshot(state), $deep(state), $isWrappable(state));import { createStore as _createStore } from "solid-js";
import { createProjection as _createProjection } from "solid-js";
import { createOptimistic as _createOptimistic } from "solid-js";
import { createOptimisticStore as _createOptimisticStore } from "solid-js";
import { reconcile as _reconcile } from "solid-js";
import { snapshot as _snapshot } from "solid-js";
import { deep as _deep } from "solid-js";
import { isWrappable as _isWrappable } from "solid-js";
const [state, setState] = _createStore({ user: { name: 'Ada' }, items: [] });
const [view] = _createProjection((draft) => {
draft.count = state.items.length;
}, { count: 0 });
const [likes, setLikes] = _createOptimistic(0);
const [todos, setTodos] = _createOptimisticStore({ list: [] });
setState((draft) => {
draft.user = _reconcile(next.user);
});
console.log(_snapshot(state), _deep(state), _isWrappable(state));createMutable and produce are gone. Store setters are draft-first.
#Children
$flatten→flatten
const flat = $flatten(props.children);import { flatten as _flatten } from "solid-js";
const flat = _flatten(props.children);#Web
From @solidjs/web:
$useHead→useHead$clientOnly→clientOnly. Like$lazy, the promise is wrapped in an arrow function:$clientOnly(import('./Chart')).
$useHead({ tag: 'title', props: { children: 'Docs' } });
const Chart = $clientOnly(import('./Chart'));import { useHead as _useHead } from "@solidjs/web";
import { clientOnly as _clientOnly } from "@solidjs/web";
_useHead({ tag: 'title', props: { children: 'Docs' } });
const Chart = _clientOnly(() => import('./Chart'));dynamic, render, hydrate, isServer, isDev, isHydrating,
isHydratable and onHydrationEnd have no CTF. Import
them from @solidjs/web.
#Components
Unshadowed component identifiers are auto-imported, in JSX and in expression
positions: For, Show, Switch, Match, Repeat, Errored, Loading,
Reveal from solid-js; Portal, HydrationScript, Hydration,
NoHydration from @solidjs/web. Dynamic is not auto-imported, because
Solid deprecates it. Use dynamic() from @solidjs/web.
const view = (
<>
<For each={list}>{(item) => <li>{item}</li>}</For>
<Show when={user} fallback={<p>Signed out</p>}>
<p>Signed in</p>
</Show>
<Switch fallback={<p>Not found</p>}>
<Match when={route === 'home'}>
<p>Home</p>
</Match>
</Switch>
<Repeat count={3}>{(index) => <i>{index}</i>}</Repeat>
<Errored fallback={<p>Failed</p>}>
<Panel />
</Errored>
<Loading fallback={<p>Loading</p>}>
<Panel />
</Loading>
<Reveal collapsed>
<Loading>
<Panel />
</Loading>
</Reveal>
<Portal mount={document.body}>
<p>Moved</p>
</Portal>
<NoHydration>
<p>Static</p>
<Hydration>
<Panel />
</Hydration>
</NoHydration>
<HydrationScript />
</>
);import { For as _For } from "solid-js";
import { Show as _Show } from "solid-js";
import { Switch as _Switch } from "solid-js";
import { Match as _Match } from "solid-js";
import { Repeat as _Repeat } from "solid-js";
import { Errored as _Errored } from "solid-js";
import { Loading as _Loading } from "solid-js";
import { Reveal as _Reveal } from "solid-js";
import { Portal as _Portal } from "@solidjs/web";
import { NoHydration as _NoHydration } from "@solidjs/web";
import { Hydration as _Hydration } from "@solidjs/web";
import { HydrationScript as _HydrationScript } from "@solidjs/web";
const view = (
<>
<_For each={list}>{(item) => <li>{item}</li>}</_For>
<_Show when={user} fallback={<p>Signed out</p>}>
<p>Signed in</p>
</_Show>
<_Switch fallback={<p>Not found</p>}>
<_Match when={route === 'home'}>
<p>Home</p>
</_Match>
</_Switch>
<_Repeat count={3}>{(index) => <i>{index}</i>}</_Repeat>
<_Errored fallback={<p>Failed</p>}>
<Panel />
</_Errored>
<_Loading fallback={<p>Loading</p>}>
<Panel />
</_Loading>
<_Reveal collapsed>
<_Loading>
<Panel />
</_Loading>
</_Reveal>
<_Portal mount={document.body}>
<p>Moved</p>
</_Portal>
<_NoHydration>
<p>Static</p>
<_Hydration>
<Panel />
</_Hydration>
</_NoHydration>
<_HydrationScript />
</>
);The solid: JSX namespace maps the same set. See
solid: namespace. Any other solid: element name is a
compile error.
#Tooling
#TypeScript
On any d.ts file, add:
/// <reference types="solid-labels" />All CTFs are declared globally, so no imports are needed.
#Disabling CTFs
solidLabels({
disabled: {
ctf: {
$signal: true,
},
},
});#Programmatic use
import { compile } from 'solid-labels/compiler';
const { code, map } = compile('App.tsx', source, { dev: true });The compiler parses with oxc-parser
and emits span edits through magic-string, so the untouched parts of your
code keep their formatting and the source map is high-resolution.