Mesa guide / Reactivity
$: watches and effects
let, const and var declare values. $: is for everything else, and it comes in
three kinds: a watch makes an imported object observable, an effect runs code
when something changes, and a writable derived is a value
const cannot express. This page covers the first two.
<script>
let count = 0
$: document.title = `Count ${count}`
$: count, (n, prev) => {
console.log(`count went from ${prev} to ${n}`)
}
</script>
<button onclick={() => count++}>{count}</button>
Automatic dependencies
$: expr and $: { block } run whenever anything they read changes. They run once
on mount too, because they find their dependencies by running.
$: document.title = `Count ${count}`
$: {
console.log(count)
localStorage.setItem('count', count)
}
Auto-tracking sees your own let and const values. It does not see inside an
imported object, so a path there needs its own watch (see below).
Explicit dependencies
$: deps, handler names what to watch, and the handler runs untracked, so reads
inside it add no dependencies. This is the form to reach for by default.
$: userId, () => load(userId)
$: (a, b), () => sync() // several dependencies go in parentheses
$: userId, (id, prevId) => log(id, prevId)
The handler is deferred. It does not run on mount, only on the first change and
every change after, so a previous value always exists. If you find yourself writing
an effect that just assigns a value from its own dependency, you wanted a
const.
A prev that holds an object holds the same reference when you mutated it in place.
Replace the object if you need the old one.
Cleanup and async
Return a function and it runs before the next call and on destroy. In an async
handler, register cleanup with $.onCleanup before the first await.
$: selectedId, async () => {
const controller = new AbortController()
$.onCleanup(() => controller.abort())
result = await (await fetch(`/api/${selectedId}`, { signal: controller.signal })).json()
}
Watches
A watch has no handler. It only says that this component re-renders when a path changes, which is how imported and in-place-mutated objects become reactive. See Shared state and Objects and arrays.
$: user.name
A $: is top-level only. Nested in a function or a block it is a compile error.
For handlers that must fire in a fixed order, see
Ordered watch groups, and for when an effect runs
relative to the DOM, Timing and the flush.
Spec: VISION.md § 4.0 · RULE 7 · RULE 60