Skip to content

Update $effect and testing docs to mention issues with push on state array #16247

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
67 changes: 67 additions & 0 deletions documentation/docs/02-runes/04-$effect.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,71 @@ Instead, use `oninput` callbacks or — better still — [function bindings](bin
</label>
```

### untrack

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](svelte#untrack).

A common mistake is updating the a `$state` array inside effects (for example `arr.push(item)`). This causes an infinite loop because methods like `push` read the array's `length` property (making the effect depend on it) and also modify it (triggering the effect again):

```svelte
<script>
let count = $state(0);
let log = $state([]);

$effect(() => {
// don't do this — infinite loop!
// push reads and writes array.length
log.push(count);

// also not fine
// reading and writing log at the same time
// log = [...log, count];
});
</script>
```

To fix this, wrap the mutation in `untrack`:

```svelte
<script>
+++ import { untrack } from 'svelte'; +++

let count = $state(0);
let log = $state([]);

$effect(() => {
// reference the state we want to track
const value = count;
// do this instead
// wrap mutations in untrack
--- log.push(count); ---
+++ untrack(() => {
log.push(value);
}); +++
});
</script>
```

This applies to all array methods that mutate the array: `push`, `pop`, `shift`, `unshift`, `splice`, etc. The same issue occurs when reassigning a `$state` variable based on its current value, such as `log = [...log, count]`.

Alternatively, if you don't need the array to be reactive (i.e., changes to it don't need to update the UI), you can use a regular variable instead of `$state`:

```svelte
<script>
--- import { untrack } from 'svelte'; ---

let count = $state(0);
--- let log = $state([]); ---
+++ let log = []; +++

$effect(() => {
// reference the state we want to track
const value = count;
// now you can safely mutate the regular variable
--- untrack(() => {
log.push(value);
}); ---
+++ log.push(count); +++
});
</script>
```
8 changes: 7 additions & 1 deletion documentation/docs/07-misc/02-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,13 @@ test('Effect', () => {
* @param {() => any} getValue
*/
export function logger(getValue) {
/** @type {any[]} */
/**
* must not be a `$state`
*
* @see https://svelte.dev/docs/svelte/$effect#When-not-to-use-$effect-untrack
*
* @type {any[]}
**/
let log = [];

$effect(() => {
Expand Down