Skip to content

Commit 77ed790

Browse files
authored
fix: ensure bind:this unmount behavior for members is conditional (#11193)
* fix: ensure bind:this unmount behavior for members is conditional * revise
1 parent e7869fa commit 77ed790

File tree

5 files changed

+69
-0
lines changed

5 files changed

+69
-0
lines changed

.changeset/weak-drinks-speak.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 bind:this unmount behavior for members is conditional

packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,15 @@ function serialize_bind_this(bind_this, context, node) {
917917

918918
/** @type {import('estree').Expression[]} */
919919
const args = [node, b.arrow([b.id('$$value'), ...ids], update), b.arrow([...ids], bind_this_id)];
920+
// If we're mutating a property, then it might already be non-existent.
921+
// If we make all the object nodes optional, then it avoids any runtime exceptions.
922+
/** @type {import('estree').Expression | import('estree').Super} */
923+
let bind_node = bind_this_id;
924+
925+
while (bind_node?.type === 'MemberExpression') {
926+
bind_node.optional = true;
927+
bind_node = bind_node.object;
928+
}
920929
if (each_ids.size) {
921930
args.push(b.thunk(b.array(Array.from(each_ids.values()).map((id) => id[1]))));
922931
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
let { item = $bindable() } = $props();
3+
</script>
4+
5+
<div bind:this={item.dom}>
6+
{item.text}
7+
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
async test({ assert, target, component }) {
6+
const [b1, b2] = target.querySelectorAll('button');
7+
8+
flushSync(() => {
9+
b1.click();
10+
b1.click();
11+
b1.click();
12+
});
13+
14+
assert.htmlEqual(
15+
target.innerHTML,
16+
`<button>add item</button><button>clear</button><div>Item 1</div><div>Item 2</div><div>Item 3</div>`
17+
);
18+
19+
flushSync(() => {
20+
b2.click();
21+
});
22+
23+
assert.htmlEqual(target.innerHTML, `<button>add item</button><button>clear</button>`);
24+
}
25+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script>
2+
import Child from './Child.svelte';
3+
let items = $state([]);
4+
5+
function add_item() {
6+
items.push({
7+
id: items.length,
8+
text: 'Item ' + (items.length + 1),
9+
dom: null,
10+
})
11+
}
12+
13+
function clear() {
14+
items = [];
15+
}
16+
</script>
17+
18+
<button on:click={add_item}>add item</button>
19+
<button on:click={clear}>clear</button>
20+
21+
{#each items as item, index (item.id)}
22+
<Child bind:item={items[index]} />
23+
{/each}

0 commit comments

Comments
 (0)