Manual UI Behaviors effect

effect

elements man ui/behaviors/effect

Reactive side effects for a controller. effect runs a function now and again whenever a reactive value it read changes, watchValue runs a callback when one value changes, and untracked reads a value without subscribing to it. A template reaches for them when something must happen as a value changes rather than be rendered: showing a native <dialog>, pushing a dismiss layer, locking scroll.

Signatures
function effect(fn: () => void): () => void; function untracked<T>(fn: () => T): T; function watchValue<T>(read: () => T, fn: (value: T, previous: T) => void): () => void;
Function Returns What it does
effect(fn) stop() Runs fn now and re-runs it whenever a reactive value it read changes. It runs detached from the render that created it, so a re-render of the parent never destroys it; only stop() does.
untracked(fn) fn's result Calls fn without subscribing the current computation to what it reads. Inside an effect, a value read this way does not re-run it.
watchValue(read, fn) stop() Runs fn(value, previous) when read() returns a different value, compared with !==, and not on the first run. The callback itself is untracked.

Start an effect in oninsert and stop it in onremove. An attribute read inside the function is tracked, so the function re-runs when the caller changes it.

In a template of your own

app/shared/templates/modal/index.ehtml
import { effect, lockScroll } from "@elements/ui/behaviors"; interface ModalState { stop?: () => void; unlock?: () => void; } function sync(dialog: HTMLDialogElement, open: boolean, ui: ModalState) { if (open) { dialog.showModal(); ui.unlock = lockScroll(); } else { dialog.close(); ui.unlock?.(); } } <Modal (open: boolean = false, private ui: ModalState = {})> <dialog oninsert={(e) => ui.stop = effect(() => sync(e.target as HTMLDialogElement, open, ui))} onremove={() => { ui.stop?.(); ui.unlock?.(); }} onclose={() => open = false}> <slot/> </dialog> </Modal>

open is read inside the effect, so the dialog opens and closes as the caller writes it, and onclose writes false back when the user presses Escape.

Used by

effect: Checkbox (opens in a new tab), ColorPicker (opens in a new tab), Field (opens in a new tab), NumberInput (opens in a new tab), RadioGroup (opens in a new tab), Select (opens in a new tab), Slider (opens in a new tab), TagsInput (opens in a new tab), Toast (opens in a new tab), ToggleGroup (opens in a new tab), Tree (opens in a new tab), VirtualList (opens in a new tab). watchValue and untracked: most templates with a controller, from Accordion (opens in a new tab) to Sidebar (opens in a new tab).