Skip to content

Commit 267339e

Browse files
committed
fix: don't re-attach the same function to the same element
1 parent 54990f2 commit 267339e

File tree

4 files changed

+70
-5
lines changed

4 files changed

+70
-5
lines changed

.changeset/red-tools-eat.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 re-`attach` the same function to the same element

packages/svelte/src/internal/client/dom/elements/attributes.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -490,19 +490,39 @@ export function attribute_effect(
490490
select_option(/** @type {HTMLSelectElement} */ (element), next.value, false);
491491
}
492492

493-
for (let symbol of Object.getOwnPropertySymbols(effects)) {
494-
if (!next[symbol]) destroy_effect(effects[symbol]);
495-
}
496-
497493
for (let symbol of Object.getOwnPropertySymbols(next)) {
498494
var n = next[symbol];
499495

500-
if (symbol.description === ATTACHMENT_KEY && (!prev || n !== prev[symbol])) {
496+
// we check if the same function is already attached to the element
497+
var prev_fn_attached_symbol =
498+
prev &&
499+
Object.getOwnPropertySymbols(prev).find(
500+
(s) => /** @type {NonNullable<typeof prev>} */ (prev)[s] === n
501+
);
502+
503+
// we only re-attach if the symbol is the ATTACHMENT_KEY
504+
// the function is different from the previous one
505+
// and the previous function is not attached to the element already
506+
if (
507+
symbol.description === ATTACHMENT_KEY &&
508+
(!prev || n !== prev[symbol]) &&
509+
!prev_fn_attached_symbol
510+
) {
501511
if (effects[symbol]) destroy_effect(effects[symbol]);
502512
effects[symbol] = branch(() => attach(element, () => n));
513+
} else if (prev_fn_attached_symbol) {
514+
// but if the previous function is attached to the element,
515+
// we need to store the old effect in the effects map at `symbol`
516+
// and delete the previous so it's not destroyed
517+
effects[symbol] = prev?.[prev_fn_attached_symbol];
518+
delete effects[prev_fn_attached_symbol];
503519
}
504520
}
505521

522+
for (let symbol of Object.getOwnPropertySymbols(effects)) {
523+
if (!next[symbol]) destroy_effect(effects[symbol]);
524+
}
525+
506526
prev = next;
507527
});
508528

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+
test({ assert, target, logs }) {
6+
const p = target.querySelector('p');
7+
const btn = target.querySelector('button');
8+
9+
assert.deepEqual(logs, ['up']);
10+
assert.equal(p?.dataset.count, '0');
11+
12+
flushSync(() => {
13+
btn?.click();
14+
});
15+
16+
assert.deepEqual(logs, ['up']);
17+
assert.equal(p?.dataset.count, '1');
18+
}
19+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script>
2+
import { createAttachmentKey } from "svelte/attachments";
3+
4+
let count = $state(0);
5+
6+
const attachment = () => {
7+
console.log("up");
8+
return () => {
9+
console.log("down");
10+
}
11+
}
12+
13+
const props = $derived({
14+
'data-count': count,
15+
[createAttachmentKey()]: attachment
16+
});
17+
</script>
18+
19+
<p {...props}></p>
20+
21+
<button onclick={() => count++}>{count}</button>

0 commit comments

Comments
 (0)