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
pull/16449/head
Simon H 2 months ago committed by GitHub
parent 05f6436445
commit 27c90dfa83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,5 @@
---
'svelte': patch
---
fix: don't reexecute derived with no dependencies on teardown

@ -653,8 +653,12 @@ export function get(signal) {
var value = derived.v;
// if the derived is dirty, or depends on the values that just changed, re-execute
if ((derived.f & CLEAN) !== 0 || depends_on_old_values(derived)) {
// if the derived is dirty and has reactions, or depends on the values that just changed, re-execute
// (a derived can be maybe_dirty due to the effect destroy removing its last reaction)
if (
((derived.f & CLEAN) === 0 && derived.reactions !== null) ||
depends_on_old_values(derived)
) {
value = execute_derived(derived);
}

@ -0,0 +1,11 @@
<script>
import { onDestroy } from 'svelte'
const { callback } = $props()
onDestroy(() => {
callback()
})
</script>
<div>teardown</div>

@ -0,0 +1,30 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
export default test({
async test({ assert, target }) {
assert.htmlEqual(
target.innerHTML,
`
<button>click</button>
<div>teardown</div>
<div>1</div>
<div>2</div>
<div>3</div>
`
);
const [increment] = target.querySelectorAll('button');
increment.click();
flushSync();
assert.htmlEqual(
target.innerHTML,
`
<button>click</button>
<div>1</div>
<div>3</div>
`
);
}
});

@ -0,0 +1,25 @@
<script>
import { SvelteSet } from 'svelte/reactivity'
import Teardown from './Teardown.svelte'
class Test {
originalIds = $state.raw([1, 2, 3])
ids = $derived(new SvelteSet(this.originalIds))
}
let show = $state(true)
const test = new Test()
function callback() {
test.ids.delete(2)
}
</script>
<button onclick={() => (show = !show)}>click</button>
{#if show}
<Teardown {callback} />
{/if}
{#each test.ids as id}
<div>{id}</div>
{/each}
Loading…
Cancel
Save