Skip to content

Commit d2530ed

Browse files
trueadmRich-Harris
andauthored
fix: handle duplicate signal dependencies gracefully (#12261)
* fix: further avoid duplicate signal dependencies visitor another approach tune tune * add test * clean up test * early return to reduce indentation, use var over let/const --------- Co-authored-by: Rich Harris <[email protected]>
1 parent ccacfc7 commit d2530ed

File tree

4 files changed

+71
-14
lines changed

4 files changed

+71
-14
lines changed

.changeset/witty-phones-retire.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: handle duplicate signal dependencies gracefully

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -417,16 +417,21 @@ function remove_reaction(signal, dependency) {
417417
* @returns {void}
418418
*/
419419
export function remove_reactions(signal, start_index) {
420-
const dependencies = signal.deps;
421-
if (dependencies !== null) {
422-
const active_dependencies = start_index === 0 ? null : dependencies.slice(0, start_index);
423-
let i;
424-
for (i = start_index; i < dependencies.length; i++) {
425-
const dependency = dependencies[i];
426-
// Avoid removing a reaction if we know that it is active (start_index will not be 0)
427-
if (active_dependencies === null || !active_dependencies.includes(dependency)) {
428-
remove_reaction(signal, dependency);
429-
}
420+
var dependencies = signal.deps;
421+
if (dependencies === null) return;
422+
423+
var active_dependencies = start_index === 0 ? null : dependencies.slice(0, start_index);
424+
var seen = new Set();
425+
426+
for (var i = start_index; i < dependencies.length; i++) {
427+
var dependency = dependencies[i];
428+
429+
if (seen.has(dependency)) continue;
430+
seen.add(dependency);
431+
432+
// Avoid removing a reaction if we know that it is active (start_index will not be 0)
433+
if (active_dependencies === null || !active_dependencies.includes(dependency)) {
434+
remove_reaction(signal, dependency);
430435
}
431436
}
432437
}
@@ -774,10 +779,7 @@ export function get(signal) {
774779
) {
775780
if (current_dependencies === null) {
776781
current_dependencies = [signal];
777-
} else if (
778-
current_dependencies[current_dependencies.length - 1] !== signal &&
779-
!current_dependencies.includes(signal)
780-
) {
782+
} else if (current_dependencies[current_dependencies.length - 1] !== signal) {
781783
current_dependencies.push(signal);
782784
}
783785
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target }) {
6+
let [btn1, btn2] = target.querySelectorAll('button');
7+
8+
flushSync(() => btn1?.click());
9+
assert.htmlEqual(
10+
target.innerHTML,
11+
`
12+
<button>toggle a</button>
13+
<button>toggle b</button>
14+
false/true/true
15+
`
16+
);
17+
18+
flushSync(() => btn2?.click());
19+
assert.htmlEqual(
20+
target.innerHTML,
21+
`
22+
<button>toggle a</button>
23+
<button>toggle b</button>
24+
`
25+
);
26+
27+
flushSync(() => btn2?.click());
28+
assert.htmlEqual(
29+
target.innerHTML,
30+
`
31+
<button>toggle a</button>
32+
<button>toggle b</button>
33+
false/true/true
34+
`
35+
);
36+
}
37+
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script>
2+
let a = $state(true);
3+
let b = $state({ c: true });
4+
5+
const x = $derived(b);
6+
</script>
7+
8+
<button onclick={() => (a = !a)}>toggle a</button>
9+
<button onclick={() => (b = b ? null : { c: true })}>toggle b</button>
10+
11+
{#if x}
12+
{a}/{x.c}/{x.c}
13+
{/if}

0 commit comments

Comments
 (0)