From 4ca9efabe3f32cccbb6ad7b605627ed43ac52a53 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 18 Mar 2025 15:30:16 -0400 Subject: [PATCH 1/3] docs: update state_unsafe_mutation message --- .../svelte/messages/client-errors/errors.md | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index ab4d1519c18c..d2acc07060eb 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -88,26 +88,31 @@ See the [migration guide](/docs/svelte/v5-migration-guide#Components-are-no-long > Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` -This error is thrown in a situation like this: +This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go: ```svelte - + + +

{count} is even: {even}

+

{count} is odd: {odd}

``` -Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable. +This is forbidden because it introduces instability: if `

{count} is even: {even}

` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived: + +```js +let even = $derived(count % 2 === 0); +let odd = $derived(!even); +``` -To fix this: -- See if it's possible to refactor your `$derived` such that the update becomes unnecessary -- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update -- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates +If side-effects are unavoidable, use [`$effect`]($effect) instead. From c83696bbce601f06d95ab9fa581890276bd083e6 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 18 Mar 2025 15:47:46 -0400 Subject: [PATCH 2/3] regenerate --- .../98-reference/.generated/client-errors.md | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 0beb3cb9a96c..35a0398279f3 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -134,26 +134,31 @@ Reading state that was created inside the same derived is forbidden. Consider us Updating state inside a derived or a template expression is forbidden. If the value should not be reactive, declare it without `$state` ``` -This error is thrown in a situation like this: +This error occurs when state is updated while evaluating a `$derived`. You might encounter it while trying to 'derive' two pieces of state in one go: ```svelte - + + +

{count} is even: {even}

+

{count} is odd: {odd}

``` -Here, the `$derived` updates `count`, which is `$state` and therefore forbidden to do. It is forbidden because the reactive graph could become unstable as a result, leading to subtle bugs, like values being stale or effects firing in the wrong order. To prevent this, Svelte errors when detecting an update to a `$state` variable. +This is forbidden because it introduces instability: if `

{count} is even: {even}

` is updated before `odd` is recalculated, `even` will be stale. In most cases the solution is to make everything derived: + +```js +let even = $derived(count % 2 === 0); +let odd = $derived(!even); +``` -To fix this: -- See if it's possible to refactor your `$derived` such that the update becomes unnecessary -- Think about why you need to update `$state` inside a `$derived` in the first place. Maybe it's because you're using `bind:`, which leads you down a bad code path, and separating input and output path (by splitting it up to an attribute and an event, or by using [Function bindings](bind#Function-bindings)) makes it possible avoid the update -- If it's unavoidable, you may need to use an [`$effect`]($effect) instead. This could include splitting parts of the `$derived` into an [`$effect`]($effect) which does the updates +If side-effects are unavoidable, use [`$effect`]($effect) instead. From ef1056977f9e17419ad918607d631c0dc9e8c1be Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 18 Mar 2025 21:22:03 -0400 Subject: [PATCH 3/3] fix example --- documentation/docs/98-reference/.generated/client-errors.md | 2 +- packages/svelte/messages/client-errors/errors.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/docs/98-reference/.generated/client-errors.md b/documentation/docs/98-reference/.generated/client-errors.md index 35a0398279f3..1735ecebbedd 100644 --- a/documentation/docs/98-reference/.generated/client-errors.md +++ b/documentation/docs/98-reference/.generated/client-errors.md @@ -143,7 +143,7 @@ This error occurs when state is updated while evaluating a `$derived`. You might let even = $state(true); let odd = $derived.by(() => { - if (count % 2 !== 0) even = false; + even = count % 2 === 0; return !even; }); diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index d2acc07060eb..01c281fc5e85 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -97,7 +97,7 @@ This error occurs when state is updated while evaluating a `$derived`. You might let even = $state(true); let odd = $derived.by(() => { - if (count % 2 !== 0) even = false; + even = count % 2 === 0; return !even; });