Skip to content

Commit fd7e8b7

Browse files
authored
fix: ensure unowned derived signals correctly re-connect to graph (#13184)
Fixes #13174. Turns out that we we skipping this important work because we return true early – thus not connecting the unowned derived back to the reaction.
1 parent 4f10c83 commit fd7e8b7

File tree

4 files changed

+112
-10
lines changed

4 files changed

+112
-10
lines changed

.changeset/rotten-actors-rush.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: ensure unowned derived signals correctly re-connect to graph

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

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -196,18 +196,20 @@ export function check_dirtiness(reaction) {
196196
update_derived(/** @type {Derived} */ (dependency));
197197
}
198198

199-
if (dependency.version > reaction.version) {
200-
return true;
199+
// If we are working with an unowned signal as part of an effect (due to !current_skip_reaction)
200+
// and the version hasn't changed, we still need to check that this reaction
201+
// is linked to the dependency source – otherwise future updates will not be caught.
202+
if (
203+
is_unowned &&
204+
current_effect !== null &&
205+
!current_skip_reaction &&
206+
!dependency?.reactions?.includes(reaction)
207+
) {
208+
(dependency.reactions ??= []).push(reaction);
201209
}
202210

203-
if (is_unowned) {
204-
// TODO is there a more logical place to do this work?
205-
if (!current_skip_reaction && !dependency?.reactions?.includes(reaction)) {
206-
// If we are working with an unowned signal as part of an effect (due to !current_skip_reaction)
207-
// and the version hasn't changed, we still need to check that this reaction
208-
// if linked to the dependency source – otherwise future updates will not be caught.
209-
(dependency.reactions ??= []).push(reaction);
210-
}
211+
if (dependency.version > reaction.version) {
212+
return true;
211213
}
212214
}
213215
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
mode: ['client'],
6+
async test({ assert, target, logs }) {
7+
let [btn1, btn2] = target.querySelectorAll('button');
8+
9+
btn1.click();
10+
btn2.click();
11+
12+
flushSync();
13+
14+
assert.htmlEqual(
15+
target.innerHTML,
16+
`<button>a</button><button>b</button><div>1</div>\ndouble:\n2`
17+
);
18+
19+
btn1.click();
20+
btn2.click();
21+
22+
flushSync();
23+
24+
assert.htmlEqual(
25+
target.innerHTML,
26+
`<button>a</button><button>b</button><div>2</div>\ndouble:\n4`
27+
);
28+
29+
btn1.click();
30+
31+
flushSync();
32+
33+
assert.htmlEqual(
34+
target.innerHTML,
35+
`<button>a</button><button>b</button><div>3</div>\ndouble:\n6`
36+
);
37+
38+
btn1.click();
39+
40+
flushSync();
41+
42+
assert.htmlEqual(
43+
target.innerHTML,
44+
`<button>a</button><button>b</button><div>4</div>\ndouble:\n8`
45+
);
46+
47+
btn1.click();
48+
49+
flushSync();
50+
51+
assert.htmlEqual(target.innerHTML, `<button>a</button><button>b</button><div>5</div>`);
52+
53+
btn1.click();
54+
55+
flushSync();
56+
57+
assert.htmlEqual(
58+
target.innerHTML,
59+
`<button>a</button><button>b</button><div>6</div>\ndouble:\n12`
60+
);
61+
62+
btn1.click();
63+
64+
flushSync();
65+
66+
assert.htmlEqual(
67+
target.innerHTML,
68+
`<button>a</button><button>b</button><div>7</div>\ndouble:\n14`
69+
);
70+
}
71+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script>
2+
let count = $state(0);
3+
4+
function increment() {
5+
count += 1;
6+
}
7+
8+
let double = $state();
9+
function setDerived() {
10+
const d = $derived(count * 2);
11+
double = {
12+
get v() { return d }
13+
};
14+
}
15+
</script>
16+
17+
<button onclick={increment}>a</button>
18+
<button onclick={setDerived}>b</button>
19+
20+
<div>{count}</div>
21+
22+
{#if double && count % 5}
23+
double: {double.v}
24+
{/if}

0 commit comments

Comments
 (0)