Skip to content

Commit 59c7487

Browse files
authored
fix: better handle array property deletion reactivity (#9921)
1 parent b779e72 commit 59c7487

File tree

4 files changed

+89
-2
lines changed

4 files changed

+89
-2
lines changed

.changeset/lovely-carpets-lick.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: better handle array property deletion reactivity

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,28 @@ const handler = {
140140

141141
deleteProperty(target, prop) {
142142
const metadata = target[STATE_SYMBOL];
143-
144143
const s = metadata.s.get(prop);
144+
const is_array = metadata.a;
145+
const boolean = delete target[prop];
146+
147+
// If we have mutated an array directly, and the deletion
148+
// was successful we will also need to update the length
149+
// before updating the field or the version. This is to
150+
// ensure any effects observing length can execute before
151+
// effects that listen to the fields – otherwise they will
152+
// operate an an index that no longer exists.
153+
if (is_array && boolean) {
154+
const ls = metadata.s.get('length');
155+
const length = target.length - 1;
156+
if (ls !== undefined && ls.v !== length) {
157+
set(ls, length);
158+
}
159+
}
145160
if (s !== undefined) set(s, UNINITIALIZED);
146161

147162
if (prop in target) update(metadata.v);
148163

149-
return delete target[prop];
164+
return boolean;
150165
},
151166

152167
get(target, prop, receiver) {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { flushSync } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `<button>push</button><button>pop</button><p>1</p><p>2</p><p>3</p>`,
6+
7+
async test({ assert, target }) {
8+
const [btn1, btn2] = target.querySelectorAll('button');
9+
10+
flushSync(() => {
11+
btn1.click();
12+
});
13+
14+
assert.htmlEqual(
15+
target.innerHTML,
16+
`<button>push</button><button>pop</button><p>1</p><p>2</p><p>3</p><p>4</p>`
17+
);
18+
19+
flushSync(() => {
20+
btn1.click();
21+
});
22+
23+
assert.htmlEqual(
24+
target.innerHTML,
25+
`<button>push</button><button>pop</button><p>1</p><p>2</p><p>3</p><p>4</p><p>5</p>`
26+
);
27+
28+
flushSync(() => {
29+
btn2.click();
30+
});
31+
32+
assert.htmlEqual(
33+
target.innerHTML,
34+
`<button>push</button><button>pop</button><p>1</p><p>2</p><p>3</p><p>4</p>`
35+
);
36+
37+
flushSync(() => {
38+
btn2.click();
39+
});
40+
41+
assert.htmlEqual(
42+
target.innerHTML,
43+
`<button>push</button><button>pop</button><p>1</p><p>2</p><p>3</p>`
44+
);
45+
46+
flushSync(() => {
47+
btn2.click();
48+
});
49+
50+
assert.htmlEqual(target.innerHTML, `<button>push</button><button>pop</button><p>1</p><p>2</p>`);
51+
}
52+
});
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script>
2+
let numbers = $state([{id: 1}, {id: 2}, {id: 3}]);
3+
</script>
4+
5+
<button onclick={() => numbers.push({id: numbers.length + 1})}>
6+
push
7+
</button>
8+
9+
<button onclick={() => numbers.pop()}>
10+
pop
11+
</button>
12+
13+
{#each numbers as number}
14+
<p>{number.id}</p>
15+
{/each}

0 commit comments

Comments
 (0)