Forgiving reactivity
Solid code keeps working, and code that Solid would break works too.
Solid code keeps working, and code that Solid would break works too. Inside a component (a PascalCase function
with at most one parameter that contains JSX), the compiler keeps these live without any ceremony
(docs/contracts/forgiving-reactivity.md in the repository):
| You write | Meaning |
|---|---|
const double = count() * 2; {double} or {double()} |
a derived value; both forms are reactive |
const { title, size = 3, label: text } = props; |
each name follows props (defaults only when undefined) |
function Row({ id, name = "anon" }) {…} |
same, for destructured parameters |
const b = a * 10; where a is derived |
chains are derived too |
const f = mode() === "up" ? up : down; {f("x")} |
a derived function stays callable |
return open() ? <A /> : <B />; return props.item && <C />; |
the returned branch follows the condition |
Rules (so it stays predictable):
- Only
constdeclarations at the top level of the component body. - Left alone: hooks and primitives (
use*,create*,batch,untrack,onMount, …), functions, classes, JSX, literals,await, and anything not read in JSX or in a closure. - A derived value is a memo: it re-runs only when the signals it read change, and caches.
- A name that is re-declared below the component's top level (a shadowing parameter, a block
const) is not rewritten. - Calling a derived const (
double()) returns its value; if the value is a function it is called. - A returned conditional or logical expression is made live only when a branch contains JSX and the condition reads
something (a call, a member such as
props.open, or a derived constant). Early returns (if (x()) return <A />; return <B />;) are left as written: use<Show>for those. A constant condition is never wrapped;/*@static*/before the condition opts out. - Switch off per file/project with
autoReactive: false(compiler) /shadow({ autoReactive: false })(Vite).
A Solid developer can ignore all of this and write createMemo style (useMemo) everywhere; both styles compile to
the same runtime calls. Two things stay Solid-like on purpose: functions whose name is not PascalCase are not
rewritten, and an early return is not made live.
src/forgiving.tsxtsx
import { useStore } from "@shadow-js/core";
export function Forgiving() {
const [count, setCount] = useStore(1);
const double = count() * 2; // Solid: static. ShadowJS: derived.
const size = count() > 2 ? "big" : "small";
const total = double + count(); // chains are derived too
return (
<button type="button" onClick={() => setCount((n) => n + 1)}>
{count()} / {double} {size} {total}
</button>
);
}