Skip to content

Commit 27c90df

Browse files
authored
fix: don't reexecute derived with no dependencies on teardown (#16438)
The prior logic was wrong because it reexecuted when something was clean, but we want to when it's not. The remaining fix was to also check the reactions: If an effect is destroyed and it was the last reaction of a derived then the derived is set to `MAYBE_DIRTY`. We therefore also need to check if the derived still has anyone listening to it, and only then reexecute it. Fixes #16363
1 parent 05f6436 commit 27c90df

File tree

5 files changed

+77
-2
lines changed

5 files changed

+77
-2
lines changed

.changeset/clever-toys-decide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: don't reexecute derived with no dependencies on teardown

packages/svelte/src/internal/client/runtime.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,12 @@ export function get(signal) {
653653

654654
var value = derived.v;
655655

656-
// if the derived is dirty, or depends on the values that just changed, re-execute
657-
if ((derived.f & CLEAN) !== 0 || depends_on_old_values(derived)) {
656+
// if the derived is dirty and has reactions, or depends on the values that just changed, re-execute
657+
// (a derived can be maybe_dirty due to the effect destroy removing its last reaction)
658+
if (
659+
((derived.f & CLEAN) === 0 && derived.reactions !== null) ||
660+
depends_on_old_values(derived)
661+
) {
658662
value = execute_derived(derived);
659663
}
660664

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<script>
2+
import { onDestroy } from 'svelte'
3+
4+
const { callback } = $props()
5+
6+
onDestroy(() => {
7+
callback()
8+
})
9+
</script>
10+
11+
<div>teardown</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
assert.htmlEqual(
7+
target.innerHTML,
8+
`
9+
<button>click</button>
10+
<div>teardown</div>
11+
<div>1</div>
12+
<div>2</div>
13+
<div>3</div>
14+
`
15+
);
16+
const [increment] = target.querySelectorAll('button');
17+
18+
increment.click();
19+
flushSync();
20+
21+
assert.htmlEqual(
22+
target.innerHTML,
23+
`
24+
<button>click</button>
25+
<div>1</div>
26+
<div>3</div>
27+
`
28+
);
29+
}
30+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script>
2+
import { SvelteSet } from 'svelte/reactivity'
3+
import Teardown from './Teardown.svelte'
4+
5+
class Test {
6+
originalIds = $state.raw([1, 2, 3])
7+
ids = $derived(new SvelteSet(this.originalIds))
8+
}
9+
10+
let show = $state(true)
11+
const test = new Test()
12+
13+
function callback() {
14+
test.ids.delete(2)
15+
}
16+
</script>
17+
18+
19+
<button onclick={() => (show = !show)}>click</button>
20+
{#if show}
21+
<Teardown {callback} />
22+
{/if}
23+
{#each test.ids as id}
24+
<div>{id}</div>
25+
{/each}

0 commit comments

Comments
 (0)