Derived values
Compute a value from other values. It runs again only when something it read changes.
Show the code
import { useMemo, useStore } from "@shadow-js/core";
export function Swatch() {
const [hue, setHue] = useStore(265);
const [light, setLight] = useStore(68);
const color = useMemo(() => `oklch(${light()}% 0.16 ${hue()})`);
return (
<div class="stack">
<div class="swatch" style={{ background: color(), blockSize: "72px", borderRadius: "12px" }} />
<label>
Hue {hue()}
<input type="range" min="0" max="360" value={hue()} onInput={(event) => setHue(event.currentTarget.valueAsNumber)} />
</label>
<label>
Lightness {light()}
<input type="range" min="30" max="90" value={light()} onInput={(event) => setLight(event.currentTarget.valueAsNumber)} />
</label>
<code>{color()}</code>
</div>
);
}
useMemo takes a function and returns a reader. Whatever the function reads, here hue() and light(), becomes its dependencies. When one changes, the memo is marked stale and computes again the next time something reads it. The result is cached until then, so any number of readers share one computation.
A plain constant is enough#
In a component, a constant computed from a signal stays live. The compiler turns it into a memo for you:
Show the code
import { useStore } from "@shadow-js/core";
export function Double() {
const [count, setCount] = useStore(4);
const double = count() * 2;
return (
<button type="button" onClick={() => setCount((n) => n + 1)}>
{count()} doubled is {double}
</button>
);
}
Write useMemo yourself when the value is expensive, when you pass it to more than one place, or when it is not at the top of a component body. Forgiving reactivity lists exactly where the compiler does this for you and where it does not.
A memo only computes#
A memo must not write a signal. It computes a value from other values, and a write inside it throws in development as WRITE_IN_MEMO. Put the write in an event handler or an effect instead.
import { createRoot, flushSync, useMemo, useStore } from "@shadow-js/core";
createRoot((dispose) => {
const [count, setCount] = useStore(3);
const double = useMemo(() => {
console.log(`computed ${count() * 2}`);
return count() * 2;
});
console.log(`read ${double()} and ${double()}`);
setCount(4);
flushSync();
console.log(`read ${double()}`);
dispose();
});
computed 6
read 6 and 6
computed 8
read 8Two reads cost one computation, and the write that follows made the next read compute again.
Go deeper#
State with useStore has derived state in stores and the selector helpers. The exact rules for memos, including lazy and the equality check, are in the reference for useMemo.