Course outline

Svelte / Basic: reactive components and ownership / Lesson 6

Effects, lifecycle and external synchronization

Use effects for owned external synchronization, keep derivation pure and clean up browser work correctly.

About 4 min readContent reviewed
On this page

Lesson goals

  • Explain synchronous dependency tracking and the browser-only effect boundary.
  • Own cleanup, async result ordering and source cancellation separately.

An effect synchronizes with something outside pure rendering

Use $effect when current component state must synchronize with an external browser capability, such as a canvas, listener or third-party widget. Prefer $derived for values fully determined by other state. Copying derived state through effects adds another update path and can create loops. Effects run in the browser and are not a mechanism to compute essential server-rendered data. Dependency tracking follows reactive reads in the synchronous execution context, so reading a value only later in an asynchronous callback is a different relationship. Review the exact documented timing rather than treating an effect as an imperative line that runs everywhere.

Return an owned cleanup function

The fragment creates one resize listener inside an effect and returns the corresponding removal. It does not claim the listener measures all layout changes; it records a browser width after setup and on resize. A real design should choose the appropriate event or observer for its actual requirement. The component was not compiled, mounted or resized here. Keep the teardown synchronous when the effect expects a cleanup return; an async effect callback returns a promise instead of that cleanup function. Async work should have a separate owned continuation and cancellation/result policy.

svelte Illustrative listener ownership and teardown
<script>
  let width = $state(0);
  $effect(() => {
    const update = () => { width = window.innerWidth; };
    update();
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  });
</script>
<p>Browser width: {width}</p>

The fragment uses a browser-only effect and matching listener identity. No listener or lifecycle event was executed.

Ignoring an old result is not the same as canceling its source

When state changes quickly, earlier asynchronous work can finish after newer work. Tie visible results to the current operation generation or equivalent ownership rule so an old response cannot overwrite a new view. Abort where supported and release owned resources, but do not assume abort reverses a source write or interrupts every driver. Authentication changes also require retiring old-principal results. Keep source deadlines and retry policy outside a generic effect that cannot know business semantics. A server mutation should use an explicit validated entry point rather than a reactive effect that may rerun for unrelated state changes.

Check current-result ownership locally

The local model starts an older and newer generation, applies the newer result first and rejects the late older result. It uses no timers or network and is deterministic. This tests a result-selection invariant, not Svelte effect scheduling, microtasks or AbortController behavior. A real component should also handle old errors and identity changes according to policy, and test cleanup when its lifetime ends. Do not rely on arbitrary sleeps to force one race; use controlled promises or test adapters so each completion order can be exercised deliberately.

javascript Complete offline result-generation rule
let generation=0;let visible="initial";
const old=++generation;const current=++generation;
function apply(id,value){if(id===generation)visible=value;}
apply(current,"current-result");apply(old,"stale-result");
console.assert(visible==="current-result");

The old result cannot replace the current value. No effect, cancellation or asynchronous task is executed.

Verify mount, rerun and teardown under the selected compiler

Svelte 5 emphasizes effect-level update lifetimes rather than assuming legacy component-wide before/after update hooks map directly to runes. Use the current documented lifecycle and tick behavior when code needs a DOM update boundary. A cleanup function should release only resources it owns and tolerate the intended rerun path. Test external integration setup, repeated dependency changes and unmount in an authorized component/browser environment. This course runs only the generation function; it does not compile effects, observe DOM timing or verify listener cleanup. Keep that evidence separate from a claim that an actual widget or subscription is leak-free.

Common errors and fixes

An effect duplicates a pure derived value

Why it happens
External synchronization and computation were conflated.
How to fix it
Use a pure derived expression for state-determined values.
Prevent it next time
Test every dependency change without extra synchronization paths.

An async effect does not return the expected cleanup

Why it happens
The callback returned a promise instead of a teardown function.
How to fix it
Keep effect setup/teardown ownership explicit and own async work separately.
Prevent it next time
Verify rerun and unmount cleanup in a real component test.

A late response restores an old account view

Why it happens
Result ownership was not tied to the current generation and principal.
How to fix it
Reject incompatible old results and retire private state.
Prevent it next time
Test identity changes while work is pending.

Check your understanding

1. Does the generation model prove a source task was canceled?

Show worked solution

No. It only decides which supplied result may become visible. External work can continue or already have committed. Use supported cancellation, resource cleanup and domain operation reconciliation separately. Verify those with an authorized adapter integration rather than interpreting a discarded result as rollback.

2. Why is $derived preferable to an effect that copies a computed total?

Show worked solution

The total is fully determined by current source values, so one pure derivation expresses its dependency directly. An effect adds mutable duplicate state and another timing path that can drift or loop. Keep effects for actual external synchronization and test the domain calculation independently from compiler tracking.

Key takeaways

  • Effects need owned external work and matching teardown.
  • Result selection, cancellation and durable effects are separate concerns.

Official references