Side effects — effect
An effect runs a function and re-runs it whenever a reactive value it reads changes. It's the third member of the reactive trio: createState holds a value, computed derives one, and effect does something when values change.
You rarely needeffectfor what's on screen — a binding (${() => …}) already updates the DOM for you. Reach foreffectto do something outside the view: persist tolocalStorage, setdocument.title, log, or drive a non-Zoijs widget.
import { createState, effect } from "./src/index.js";
const theme = createState("light");
// Runs now, and again every time `theme` changes:
effect(() => localStorage.setItem("theme", theme.get()));
theme.set("dark"); // → writes "dark" to localStorageIt tracks dependencies automatically#
Whatever the function reads becomes a dependency — there is no dependency array (and none of the bugs that come with one). Read a.get() and b.get() and the effect re-runs when either changes; stop reading one and it stops depending on it. It runs once immediately, then again (batched on a microtask) on each change.
Cleanup: return a function#
If your effect sets something up that needs tearing down, return a cleanup function. It runs before the next run and once more on dispose — the same convention as a ref.
effect(() => {
const id = setInterval(() => poll(query.get()), 1000);
return () => clearInterval(id); // before each re-run, and on dispose
});This is the difference from onCleanup: a returned cleanup is per-run (it fires every time the effect re-runs), while onCleanup fires once, when the component unmounts. Use the return value for things you set up inside the effect.
It cleans up with its owner#
An effect created inside a component or list item is disposed automatically when that component unmounts or the item is removed — like everything else in Zoijs. You usually don't manage its lifetime.
Created at module top level (outside any component), it lives until you stop it yourself with the returned handle:
const handle = effect(() => (document.title = `${unread.get()} unread`));
handle.dispose(); // stop iteffect vs. a binding#
| You want… | Use |
|---|---|
| reactive text or attributes on screen | a binding — ${() => value.get()} |
| a side effect off screen (storage, title, logging, a widget) | effect(() => …) |
If you find yourself poking the DOM by hand inside an effect, that's usually a sign a binding belongs there instead.
effect is not a lifecycle system#
There is no onMounted / onUpdated / beforeUnmount family. Zoijs has just two tools here: effect (react to change) and onCleanup (tear down). Together they cover the space without a lifecycle API.