Skip to content

Commit 09527fd

Browse files
trueadmRich-Harris
andauthored
fix: avoid disconnecting deriveds that are still active (#13292)
* fix: ensure the if block condition is wrapped in a derived * add test * tidy * fix key block * fix key block test * alternative approach * changeset * Update packages/svelte/src/internal/client/runtime.js Co-authored-by: Rich Harris <[email protected]> --------- Co-authored-by: Rich Harris <[email protected]>
1 parent 9d1394f commit 09527fd

File tree

4 files changed

+53
-1
lines changed

4 files changed

+53
-1
lines changed

.changeset/dirty-worms-type.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: avoid disconnecting deriveds that are still active

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,14 @@ function remove_reaction(signal, dependency) {
370370
}
371371
// If the derived has no reactions, then we can disconnect it from the graph,
372372
// allowing it to either reconnect in the future, or be GC'd by the VM.
373-
if (reactions === null && (dependency.f & DERIVED) !== 0) {
373+
if (
374+
reactions === null &&
375+
(dependency.f & DERIVED) !== 0 &&
376+
// Destroying a child effect while updating a parent effect can cause a dependency to appear
377+
// to be unused, when in fact it is used by the currently-updating parent. Checking `new_deps`
378+
// allows us to skip the expensive work of disconnecting and immediately reconnecting it
379+
(new_deps === null || !new_deps.includes(dependency))
380+
) {
374381
set_signal_status(dependency, MAYBE_DIRTY);
375382
// If we are working with a derived that is owned by an effect, then mark it as being
376383
// disconnected.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
await Promise.resolve();
7+
8+
let [btn1] = target.querySelectorAll('button');
9+
10+
flushSync(() => {
11+
btn1?.click();
12+
});
13+
14+
assert.htmlEqual(
15+
target.innerHTML,
16+
`false\ntrue\n<button>Toggle</button>\nfirst:\nfalse\n<br>\nsecond:\ntrue`
17+
);
18+
}
19+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script>
2+
let first = $state(true)
3+
let second = $state(false)
4+
let derivedSecond = $derived(second)
5+
6+
queueMicrotask(() => {
7+
first = false
8+
});
9+
</script>
10+
11+
{first} {second}
12+
13+
<button onclick={() => {
14+
second = true
15+
}}>Toggle</button>
16+
17+
{#if first || derivedSecond}
18+
first: {first}
19+
<br />
20+
second: {derivedSecond}
21+
{/if}

0 commit comments

Comments
 (0)