Skip to content

Commit 1b074f2

Browse files
committed
fix: don't reexecute derived with no dependencies on teardown
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 1b074f2

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)