-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Description
Describe the bug
I think the explanation of when not to use $effect's is rather lacking in terms of the exact reasons why. Instead of just saying don't do this..., which is fine, it would more helpful for learning to understand the exact reasons.
This section: https://svelte.dev/docs/svelte/$effect#When-not-to-use-$effect
Starting a summary of the reasons:
- Using effects is generally more overhead due to how it works internally, and signals in general, versus using something like simple callbacks. For example:
- Effects have to run at least once regardless whether or not they read any states / signals.
- if you return a function (dispose function) from an $effect, it runs not only when a component is destroyed but every time before the $effect is executed again.
- Even if you don't return a dispose function, internally, before the $effect is run again, its own dispose actions have to run, like removing all previous subscriptions.
- Effects run every time any of the observable states / signals change, and every time subscriptions to these changes have to be set up anew.
- Mutating states / signals inside effects can cause unintended reads of these states / signals, thus causing unintended subscriptions. In turn, these will lead to infinite loops. Using
untrack()or async code should remedy such situations, however, it may not be sufficient (see below). - If mutating the same states / signals that are observed by the same $effect, even if mutations happen inside
untrack()or async code, it will lead to an infinite loop. It can be remedied by using conditional mutations. - Mutating state inside effects across multiple effects can lead to circular logic. For example, given two states
aandbwith 2 separate effects for each, if the effect foramutatesb, it will causeb's effect to run and ifb's effect mutatesa, it will cause thea's effect to run, thus causing an infinite loop. Conditional mutations can resolve such infinite loops due to circular logic, however, they may be difficult to discover across multiple effects and components. - Understanding the timing of when effects run is important to avoid unexpected behavior.
$effect()runs only after components mount or after the template (which is also a type of effect) is updated, whereas$effect.pre()is executed before.- Effects are completely skipped and not executed in SSR mode. Even changing any of the state / signals variables will not cause the effects to run.
In summary, there are 3 main reasons: usage overhead, infinite loops because of mutations and timing. I broke out each infinite loop case separately for easier reference to each but they can just be under one section.
Anything else?
Also, the phrase at the end of this section is not accurate because of the reason 3. above:
If you absolutely have to update $state within an effect and run into an infinite loop because you read and write to the same $state, use [untrack](https://svelte.dev/docs/svelte/svelte#untrack).
Reproduction
docs
Logs
No response
System Info
docsSeverity
annoyance