Skip to content

Commit

Permalink
breaking: disallow state mutations in logic block expression (#13625)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm authored Oct 16, 2024
1 parent 7429854 commit 2070c8a
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/eighty-dryers-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

breaking: disallow state mutations in logic block expression
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,5 @@ Reading state that was created inside the same derived is forbidden. Consider us
### state_unsafe_mutation

```
Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`
Updating state inside a derived or logic block expression is forbidden. If the value should not be reactive, declare it without `$state`
```
2 changes: 1 addition & 1 deletion packages/svelte/messages/client-errors/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,4 @@
## state_unsafe_mutation

> Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`
> Updating state inside a derived or logic block expression is forbidden. If the value should not be reactive, declare it without `$state`
6 changes: 3 additions & 3 deletions packages/svelte/src/internal/client/dom/blocks/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import {
pause_effect,
resume_effect
} from '../../reactivity/effects.js';
import { source, mutable_source, set } from '../../reactivity/sources.js';
import { source, mutable_source, internal_set } from '../../reactivity/sources.js';
import { array_from, is_array } from '../../../shared/utils.js';
import { INERT } from '../../constants.js';
import { queue_micro_task } from '../task.js';
Expand Down Expand Up @@ -463,11 +463,11 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
*/
function update_item(item, value, index, type) {
if ((type & EACH_ITEM_REACTIVE) !== 0) {
set(item.v, value);
internal_set(item.v, value);
}

if ((type & EACH_INDEX_REACTIVE) !== 0) {
set(/** @type {Value<number>} */ (item.i), index);
internal_set(/** @type {Value<number>} */ (item.i), index);
} else {
item.i = index;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/svelte/src/internal/client/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,12 +343,12 @@ export function state_unsafe_local_read() {
}

/**
* Updating state inside a derived is forbidden. If the value should not be reactive, declare it without `$state`
* Updating state inside a derived or logic block expression is forbidden. If the value should not be reactive, declare it without `$state`
* @returns {never}
*/
export function state_unsafe_mutation() {
if (DEV) {
const error = new Error(`state_unsafe_mutation\nUpdating state inside a derived is forbidden. If the value should not be reactive, declare it without \`$state\``);
const error = new Error(`state_unsafe_mutation\nUpdating state inside a derived or logic block expression is forbidden. If the value should not be reactive, declare it without \`$state\``);

error.name = 'Svelte error';
throw error;
Expand Down
15 changes: 13 additions & 2 deletions packages/svelte/src/internal/client/reactivity/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
BRANCH_EFFECT,
INSPECT_EFFECT,
UNOWNED,
MAYBE_DIRTY
MAYBE_DIRTY,
BLOCK_EFFECT
} from '../constants.js';
import * as e from '../errors.js';

Expand Down Expand Up @@ -136,14 +137,24 @@ export function set(source, value) {
if (
active_reaction !== null &&
is_runes() &&
(active_reaction.f & DERIVED) !== 0 &&
(active_reaction.f & (DERIVED | BLOCK_EFFECT)) !== 0 &&
// If the source was created locally within the current derived, then
// we allow the mutation.
(derived_sources === null || !derived_sources.includes(source))
) {
e.state_unsafe_mutation();
}

return internal_set(source, value);
}

/**
* @template V
* @param {Source<V>} source
* @param {V} value
* @returns {V}
*/
export function internal_set(source, value) {
if (!source.equals(value)) {
source.v = value;
source.version = increment_version();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
compileOptions: {
dev: true
},

test({ assert, target }) {
const button = target.querySelector('button');

assert.throws(() => {
button?.click();
flushSync();
}, /state_unsafe_mutation/);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script>
let items = $state([]);
</script>

<button onclick={() => items.push(3, 2, 1)}>Add</button>
{#each items.sort() as item (item)}
<p>{item}</p>
{/each}

0 comments on commit 2070c8a

Please sign in to comment.