Effect
pp.effect(callback, deps?) runs side effects after render — subscriptions, timers, data loading, DOM measurements. It may return a cleanup function, which runs before the next execution and on component disposal.
The canonical shape: attach in the body, detach in the cleanup
<section pp-component="stopwatch_1">
<p>Elapsed: {seconds}s</p>
<button onclick="setRunning(!running)">{running ? "Pause" : "Resume"}</button>
<script>
const [seconds, setSeconds] = pp.state(0);
const [running, setRunning] = pp.state(true);
pp.effect(() => {
if (!running) return;
const id = setInterval(() => setSeconds((s) => s + 1), 1000);
return () => clearInterval(id);
}, [running]);
</script>
</section>
Dependencies
| Form | Runs |
|---|---|
pp.effect(fn) |
After every render. |
pp.effect(fn, []) |
Once after mount; cleanup runs on disposal. |
pp.effect(fn, [a, b]) |
After mount and whenever a or b changes. |
Prefer an explicit dependency array — it makes the effect's schedule readable and predictable.
Loading data on mount
<section pp-component="user_panel">
<p hidden="{!loading}">Loading…</p>
<p hidden="{loading || !user}">Signed in as {user?.name}</p>
<script>
const [user, setUser] = pp.state(null);
const [loading, setLoading] = pp.state(true);
pp.effect(() => {
let cancelled = false;
pp.rpc("currentUser").then((result) => {
if (cancelled) return;
setUser(result);
setLoading(false);
});
return () => { cancelled = true; };
}, []);
</script>
</section>
The cleanup flag discards a response that lands after the component is gone or after a newer request started — the standard stale-response guard.
Rules
- Cleanup must be synchronous. Returning a promise (an
asynceffect) warns and the cleanup is ignored — start async work inside the effect instead. pp.layoutEffecthas the same signature but runs synchronously after DOM mutation and beforepp.effect— use it only for measurements that must happen before paint.- An effect that only derives a value from state is not an effect — compute it inline or with
pp.memoduring render. - Effects run per component instance, and cleanups run on disposal — which is why sockets and observers opened in effects never leak.