Skip to content

docs: address $effect feedback #15107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@
title: $effect
---

Effects are what make your application _do things_. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.
Effects are used for making your application perform _actions_. You might use it to do things like call a third-party library, draw on a `<canvas>` element, or make a network request. It's important to distinguish these actions from updating state, which should not be done inside an effect.

Most of the effects in a Svelte app are created by Svelte itself — they're the bits that update the text in `<h1>hello {name}!</h1>` when `name` changes, for example.
> [!NOTE] Your application will become a nightmare to deal with almost immediately if you try to update state inside an effect. If you're tempted to use an effect to update state, see [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this wording feels a bit strong. if we're so adamant that you shouldn't update state inside an effect, why do we allow it? it also feels quite vague — what is the nightmare exactly? it would be good if we could articulate why we warn against this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we're so adamant that you shouldn't update state inside an effect, why do we allow it?

I would love if someone could answer why we allow it. It causes so many problems and we could save users from themselves

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it is unavoidable in some scenarios. Angular actually had a setting where writing inside effects was disallowed and they removed it because it caused so many headaches (and didn't actually help, most people just turned on the option)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be useful to have an example of where it is unavoidable. I have yet to see such a scenario - though it can sometimes be tricky to figure out how to write the code in such a way where you avoid it (e.g. needing the trick Paolo was sharing) and I frequently see people using it as a shortcut

Copy link

@ahkelly ahkelly Feb 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the correct solution then to sveltejs/kit#12999 if not 3.2? I would never need to use $effect() if this was solved. Offering the boiler plate "use a callback" as an option has not worked for me in very specific situations.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what you are saying but I am not writing to data but reading from the data. Using $derived.by is not picking up the newest invalidated data in certain circumstances like I note above. In these situations only $effect is picking up the change. In 95%+ of situations simply using data.variable_name will always give you the reactive data value like it did 100% of the time in svelte 4. In the other 5%, which you can find numerous reports in the discord of the same since the svelte 5 migration only using $effect works as noted in the issue I provided as well as here #14536 (comment).

I only waste bandwidth here on this as the maintainers seem hellbent on removing $effect (and seemingly bind:), which I am ok with but they need to first fix these lingering edge cases that are new to svelte 5/kit. I apologize for the interruption and wasting of your time on this, please don't waste anymore time on it as I am sure others (than me) with more influence/understanding of the issue will eventually solve/revert it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using $derived.by is not picking up the newest invalidated data in certain circumstances like I note above

What i'm asking you then is to open an issue with the reproduction of this...because to me it doesn't make sense since the dependency resolution for both derived and effect is the same. If you can provide such example it would be very valuable since if this is a bug we need to fix it. The steps to reproduce you provided are vague and i don't see why doing them with derived instead of effect should change anything.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Completely understand and thank you. I will endeavour to come up with a generic reproduction but not sure how I simulate the data request to our backend in a repl.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use https://sveltelab.dev to make a sveltekit reproduction...and obviously your backend should not be involved in the minimal reproduction. Just try to reproduce the "async behavior" you are seeing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #15570


But you can also create your own effects with the `$effect` rune, which is useful when you need to synchronize an external system (whether that's a library, or a `<canvas>` element, or something across a network) with state inside your Svelte app.

> [!NOTE] Avoid overusing `$effect`! When you do too much work in effects, code often becomes difficult to understand and maintain. See [when not to use `$effect`](#When-not-to-use-$effect) to learn about alternative approaches.

Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):
You can create an effect with the `$effect` rune ([demo](/playground/untitled#H4sIAAAAAAAAE31S246bMBD9lZF3pSRSAqTVvrCAVPUP2sdSKY4ZwJJjkD0hSVH-vbINuWxXfQH5zMyZc2ZmZLVUaFn6a2R06ZGlHmBrpvnBvb71fWQHVOSwPbf4GS46TajJspRlVhjZU1HqkhQSWPkHIYdXS5xw-Zas3ueI6FRn7qHFS11_xSRZhIxbFtcDtw7SJb1iXaOg5XIFeQGjzyPRaevYNOGZIJ8qogbpe8CWiy_VzEpTXiQUcvPDkSVrSNZz1UlW1N5eLcqmpdXUvaQ4BmqlhZNUCgxuzFHDqUWNAxrYeUM76AzsnOsdiJbrBp_71lKpn3RRbii-4P3f-IMsRxS-wcDV_bL4PmSdBa2wl7pKnbp8DMgVvJm8ZNskKRkEM_OzyOKQFkgqOYBQ3Nq89Ns0nbIl81vMFN-jKoLMTOr-SOBOJS-Z8f5Y6D1wdcR8dFqvEBdetK-PHwj-z-cH8oHPY54wRJ8Ys7iSQ3Bg3VA9azQbmC9k35kKzYa6PoVtfwbbKVnBixBiGn7Pq0rqJoUtHiCZwAM3jdTPWCVtr_glhVrhecIa3vuksJ_b7TqFs4DPyriSjd5IwoNNQaAmNI-ESfR2p8zimzvN1swdCkvJHPH6-_oX8o1SgcIDAAA=)):

```svelte
<script>
Expand All @@ -32,7 +28,11 @@ Your effects run after the component has been mounted to the DOM, and in a [micr
<canvas bind:this={canvas} width="100" height="100" />
```

Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.
When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed inside [`untrack`](svelte#untrack)), and re-runs the function when that state later changes.

> [!NOTE] If you're having difficulty understanding why your `$effect` is rerunning or is not running see [understanding dependencies](#Understanding-dependencies). Effects are triggered differently than the `$:` blocks you may be used to if coming from Svelte 4.

Your effects run after the component has been mounted to the DOM, and in a [microtask](https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide) after state changes. Re-runs are batched (i.e. changing `color` and `size` in the same moment won't cause two separate runs), and happen after any DOM updates have been applied.

You can place `$effect` anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed).

Expand Down
2 changes: 2 additions & 0 deletions documentation/docs/07-misc/07-v5-migration-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ A `$:` statement could also be used to create side effects. In Svelte 5, this is
</script>
```

Note that [when `$effect` runs is different]($effect#Understanding-dependencies) than when `$:` runs.

> [!DETAILS] Why we did this
> `$:` was a great shorthand and easy to get started with: you could slap a `$:` in front of most code and it would somehow work. This intuitiveness was also its drawback the more complicated your code became, because it wasn't as easy to reason about. Was the intent of the code to create a derivation, or a side effect? With `$derived` and `$effect`, you have a bit more up-front decision making to do (spoiler alert: 90% of the time you want `$derived`), but future-you and other developers on your team will have an easier time.
>
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte/src/index-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { legacy_mode_flag } from './internal/flags/index.js';
*
* `onMount` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render).
*
* `onMount` runs only once. If you need reactivity, you can use [`$effect`](https://svelte.dev/docs/svelte/$effect),
* which also runs when the component is mounted to the DOM.
*
* @template T
* @param {() => NotFunction<T> | Promise<NotFunction<T>> | (() => any)} fn
* @returns {void}
Expand Down
3 changes: 3 additions & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ declare module 'svelte' {
*
* `onMount` does not run inside [server-side components](https://svelte.dev/docs/svelte/svelte-server#render).
*
* `onMount` runs only once. If you need reactivity, you can use [`$effect`](https://svelte.dev/docs/svelte/$effect),
* which also runs when the component is mounted to the DOM.
*
* */
export function onMount<T>(fn: () => NotFunction<T> | Promise<NotFunction<T>> | (() => any)): void;
/**
Expand Down
Loading