Skip to content

Commit 5eb8641

Browse files
fix: keep default values of props a proxy after reassignment (#11860)
* fix: keep default values of props a proxy after reassignment * fix: make this work for non bindable props too * chore: more comprehensive test * chore: cast away * chore: better variable name and check * chore: fix lint
1 parent 0a7de87 commit 5eb8641

File tree

4 files changed

+99
-6
lines changed

4 files changed

+99
-6
lines changed

.changeset/empty-horses-tell.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: keep default values of props a proxy after reassignment

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,13 @@ export function serialize_set_binding(node, context, fallback, options) {
292292

293293
const serialize = () => {
294294
if (left === node.left) {
295-
if (binding.kind === 'prop' || binding.kind === 'bindable_prop') {
295+
const is_initial_proxy =
296+
binding.initial !== null &&
297+
should_proxy_or_freeze(
298+
/**@type {import("estree").Expression}*/ (binding.initial),
299+
context.state.scope
300+
);
301+
if ((binding.kind === 'prop' || binding.kind === 'bindable_prop') && !is_initial_proxy) {
296302
return b.call(left, value);
297303
} else if (is_store) {
298304
return b.call('$.store_set', serialize_get_binding(b.id(left_name), state), value);
@@ -318,6 +324,18 @@ export function serialize_set_binding(node, context, fallback, options) {
318324
? b.call('$.freeze', value)
319325
: value
320326
);
327+
} else if (
328+
(binding.kind === 'prop' || binding.kind === 'bindable_prop') &&
329+
is_initial_proxy
330+
) {
331+
call = b.call(
332+
left,
333+
context.state.analysis.runes &&
334+
!options?.skip_proxy_and_freeze &&
335+
should_proxy_or_freeze(value, context.state.scope)
336+
? serialize_proxy_reassignment(value, left_name, state)
337+
: value
338+
);
321339
} else {
322340
call = b.call('$.set', b.id(left_name), value);
323341
}
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
<script>
2-
/** @type {{ object?: { count: number }}} */
3-
let { object = $bindable({ count: 0 }) } = $props();
2+
/** @type {{ object?: { count: number }, non_bindable?: { count: number }}} */
3+
let { object = $bindable({ count: 0 }), non_bindable = { count: 0 } } = $props();
44
</script>
55

6-
<button onclick={() => object.count += 1}>
6+
<button onclick={() => (object.count += 1)}>
77
mutate: {object.count}
88
</button>
99

10-
<button onclick={() => object = { count: object.count + 1 } }>
10+
<button onclick={() => (object = { count: object.count + 1 })}>
1111
reassign: {object.count}
1212
</button>
13+
14+
<button onclick={() => (non_bindable.count += 1)}>
15+
mutate: {non_bindable.count}
16+
</button>
17+
18+
<button onclick={() => (non_bindable = { count: non_bindable.count + 1 })}>
19+
reassign: {non_bindable.count}
20+
</button>

packages/svelte/tests/runtime-runes/samples/props-default-reactivity/_config.js

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ export default test({
55
html: `
66
<button>mutate: 0</button>
77
<button>reassign: 0</button>
8+
<button>mutate: 0</button>
9+
<button>reassign: 0</button>
810
`,
911

1012
async test({ assert, target }) {
11-
const [btn1, btn2] = target.querySelectorAll('button');
13+
const [btn1, btn2, btn3, btn4] = target.querySelectorAll('button');
1214

1315
flushSync(() => {
1416
btn1?.click();
@@ -19,6 +21,8 @@ export default test({
1921
`
2022
<button>mutate: 1</button>
2123
<button>reassign: 1</button>
24+
<button>mutate: 0</button>
25+
<button>reassign: 0</button>
2226
`
2327
);
2428

@@ -31,6 +35,64 @@ export default test({
3135
`
3236
<button>mutate: 2</button>
3337
<button>reassign: 2</button>
38+
<button>mutate: 0</button>
39+
<button>reassign: 0</button>
40+
`
41+
);
42+
43+
flushSync(() => {
44+
btn1?.click();
45+
});
46+
47+
assert.htmlEqual(
48+
target.innerHTML,
49+
`
50+
<button>mutate: 3</button>
51+
<button>reassign: 3</button>
52+
<button>mutate: 0</button>
53+
<button>reassign: 0</button>
54+
`
55+
);
56+
57+
flushSync(() => {
58+
btn3?.click();
59+
});
60+
61+
assert.htmlEqual(
62+
target.innerHTML,
63+
`
64+
<button>mutate: 3</button>
65+
<button>reassign: 3</button>
66+
<button>mutate: 1</button>
67+
<button>reassign: 1</button>
68+
`
69+
);
70+
71+
flushSync(() => {
72+
btn4?.click();
73+
});
74+
75+
assert.htmlEqual(
76+
target.innerHTML,
77+
`
78+
<button>mutate: 3</button>
79+
<button>reassign: 3</button>
80+
<button>mutate: 2</button>
81+
<button>reassign: 2</button>
82+
`
83+
);
84+
85+
flushSync(() => {
86+
btn3?.click();
87+
});
88+
89+
assert.htmlEqual(
90+
target.innerHTML,
91+
`
92+
<button>mutate: 3</button>
93+
<button>reassign: 3</button>
94+
<button>mutate: 3</button>
95+
<button>reassign: 3</button>
3496
`
3597
);
3698
}

0 commit comments

Comments
 (0)