skip to content

In a component framework, what is an effect, and when does the cleanup it registers run?

level: juniorimportance: must knowfreq 74%

answer

  1. side work, not part of rendering
  2. runs after the update is applied
  3. if a run starts something, it must stop it
  4. cleanup before each re-run, and at teardown

basics

~20 s

An effect is code a component framework runs after a state change so something outside the component agrees with the new state - a timer, a listener, the document title. Its cleanup runs before each re-run and once at teardown.

solid answer

~50 s

An effect is the framework's bridge to systems it does not own. Rendering describes what the UI should look like and is meant to stay pure; the effect performs the side work that follows - starting a timer, attaching a listener, opening a subscription, writing the document title, pushing a value into an imperative widget. The runtime runs it *after* the update has been applied, so it observes committed state rather than a render in progress. Because an effect usually starts something, it registers a cleanup that stops it, and the runtime calls that cleanup in two situations: immediately before the effect runs again, so run N's timer is cancelled before run N+1 opens its own, and once when the effect's owner is torn down. Skip the cleanup and every re-run stacks another timer or subscription on the last.

go deeper

for a junior

Be able to say it in one breath: an effect is side work that runs after state changes, and its cleanup undoes what that run started. Name two examples - a timer and a listener.

for a middle

Explain the timing precisely: after the update is applied, again when inputs change, with the previous cleanup called before the next setup. Show why that ordering prevents two live subscriptions.

for a senior

Diagnose from the symptom. Work that happens N times where N grows with session length points at a missing per-run cleanup, not at a wrong setup. Know how you would prove it.

for a principal

Set the team rule: every effect that opens something owns closing it, and review looks at the cleanup first. Decide where effects are allowed at all versus what belongs in handlers or derivations.

## What an effect is A component framework splits work in two. **Rendering** is a description: given the current state, here is what the UI should look like. It is expected to be pure — no timers started, no listeners attached, no messages sent — because the runtime may run it more than once, discard a result, or run it for an update the user never sees. That leaves everything the description cannot express: the world outside the component tree. An **effect** is the slot for that work. It is code the runtime runs *because state changed*, whose job is to make some external thing agree with the new state. Typical external things an effect synchronizes: - a **subscription** to something that pushes values (a connection, an event source, an imperative widget's change notification); - a **timer** or an animation loop that must exist only while a particular state holds; - a **listener** on a host node or a global object the component does not render; - a value the component does not own but must reflect — the document title, the URL, persistent storage, a third-party map or editor instance that has its own imperative interface. The shared shape is *setup now, undo later*. That is why cleanup is half of the contract rather than an optional extra. ## When the runtime runs it - **After the update it belongs to has been applied**, not during the render that computed it. By the time the effect runs, the state it reads is the committed state, and the host output for that update exists. - **Again whenever its inputs change**, by whichever mechanism the runtime uses to learn those inputs. - **Never as part of producing the output**, which is what keeps render re-runnable. Some runtimes also offer a variant that runs earlier — before the user sees the new frame — for work that must adjust the host output before it is painted. That timing is a property of the commit phase rather than of effects as such, and the rule below is the same for both variants. ## The cleanup contract An effect may register a cleanup: a function that undoes what *that particular run* set up. The runtime calls it in two situations, and reasoning about both is what separates a correct effect from a leaky one. | Trigger | What the runtime does | What the cleanup must release | |---|---|---| | The effect is about to run again | Calls the previous run's cleanup **first**, then the new setup | Exactly what the previous run created: that timer, that subscription, that listener | | The effect's owner is torn down | Calls the last run's cleanup once | The same things, plus anything the effect intends to outlive nothing | Two consequences follow directly: 1. **Cleanup is per run, not per lifetime.** The pairing is run N's setup with run N's cleanup. If an effect re-runs five times, five setups and five cleanups happen, interleaved. 2. **Ordering is cleanup-then-setup.** Old subscription closed before the new one opens, so the two never overlap — which matters when the external system would otherwise deliver values for both at once. Some runtimes deliberately run setup and cleanup an extra time during development precisely to make a missing or wrong cleanup fail loudly on the first try instead of in production. ## What breaks without it - A repeating timer per run, so the callback fires two, three, ten times per interval. - A listener added on every re-run, so one user gesture triggers the handler many times. - A subscription that outlives the component, still receiving values and still writing to state that no longer has anywhere to go. - An imperative widget left configured for state that has since changed, drifting further out of agreement with the component on every update. The symptom is almost always *work happening N times where N grows with how long the user has been on the page* — a strong hint that a cleanup is missing rather than a setup being wrong. ## What does not belong in an effect An effect is for telling the outside world something. It is not for: - **computing a value the UI needs** — that is a derivation from state, computed where the output is described; - **reacting to a user action** — that belongs in the handler for the action, where the intent is explicit and runs once; - **copying an input into state** so the component can "own" it, which creates two sources of truth that drift. A useful test: if removing the effect only breaks something *inside* the component tree, it probably should not have been an effect. ## Where the models differ Frameworks disagree about how an effect learns it should re-run. One family re-runs the component function and compares an author-declared list of values; another records, at read time, which reactive values the effect touched and re-runs on a write to one of those; a third instruments the code at build time to wire the same thing up statically. The *cleanup* contract is remarkably stable across all three: whatever decides when to re-run, the run that opened something is the run that must close it.

  • If an effect's work starts nothing that keeps running - setting the document title, say - does it still need a cleanup?
    Usually not: cleanup exists to stop things that are still going. The exception is when the effect overwrote a shared value that something else also owns; then the honest cleanup restores what was there before, so tearing the component down does not leave the rest of the app looking at a title or a global setting it never asked for.
  • Does the runtime call the previous cleanup before or after the next setup, and why does the order matter?
    Before. The pairing is run N's cleanup, then run N+1's setup, so the old subscription is closed before the new one opens and the two never deliver at the same time. If the order were reversed you would briefly hold two live connections, and any value arriving in that window could be attributed to the wrong one.

Each run of an effect is a lease it signs with the outside world, and the cleanup is handing the keys back. Sign a new lease without returning the old keys and you are paying for two flats at once.

saying these in an interview costs you the question

  • Thinks an effect runs during render, before the update is applied
  • Believes cleanup only runs when the component is destroyed, not between re-runs
  • Returns no cleanup for a timer or listener and assumes the framework cancels it
  • Uses an effect to compute a value that could simply be derived from state
  • Puts work triggered by a click into an effect watching the resulting state