Skip to content

Commit db8cba3

Browse files
authored
fix: react to mutated slot props in legacy mode (#10197)
If a list is passed to a component and an item of that list is passed as a slot prop back up, then mutating a property of that item did not result in a rerun. The reason was that derived is using object identity equality, resulting in the value not being updated. To fix it, we use safe-equals in this situations instead.
1 parent b94d72b commit db8cba3

File tree

7 files changed

+68
-1
lines changed

7 files changed

+68
-1
lines changed

.changeset/olive-shirts-complain.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: react to mutated slot props in legacy mode

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2886,7 +2886,12 @@ export const template_visitors = {
28862886
const name = node.expression === null ? node.name : node.expression.name;
28872887
return b.const(
28882888
name,
2889-
b.call('$.derived', b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))))
2889+
b.call(
2890+
// in legacy mode, sources can be mutated but they're not fine-grained.
2891+
// Using the safe-equal derived version ensures the slot is still updated
2892+
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
2893+
b.thunk(b.member(b.id('$$slotProps'), b.id(node.name)))
2894+
)
28902895
);
28912896
}
28922897
},

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,6 +1307,18 @@ export function derived(init) {
13071307
return signal;
13081308
}
13091309

1310+
/**
1311+
* @template V
1312+
* @param {() => V} init
1313+
* @returns {import('./types.js').ComputationSignal<V>}
1314+
*/
1315+
/*#__NO_SIDE_EFFECTS__*/
1316+
export function derived_safe_equal(init) {
1317+
const signal = derived(init);
1318+
signal.e = safe_equal;
1319+
return signal;
1320+
}
1321+
13101322
/**
13111323
* @template V
13121324
* @param {V} initial_value

packages/svelte/src/internal/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export {
77
source,
88
mutable_source,
99
derived,
10+
derived_safe_equal,
1011
prop,
1112
user_effect,
1213
render_effect,
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script>
2+
export let things;
3+
</script>
4+
5+
<div>
6+
{#each things as thing}
7+
<slot {thing}/>
8+
{/each}
9+
</div>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { tick } from 'svelte';
2+
import { test } from '../../test';
3+
4+
export default test({
5+
html: `
6+
<button>mutate</button>
7+
<div>
8+
<span>hello</span>
9+
</div>
10+
`,
11+
12+
async test({ assert, target }) {
13+
target.querySelector('button')?.click();
14+
await tick();
15+
assert.htmlEqual(
16+
target.innerHTML,
17+
`
18+
<button>mutate</button>
19+
<div>
20+
<span>bye</span>
21+
</div>
22+
`
23+
);
24+
}
25+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<script>
2+
import Nested from './Nested.svelte';
3+
4+
let things = [{ text: 'hello' }];
5+
</script>
6+
7+
<button on:click={() => things[0].text = 'bye'}>mutate</button>
8+
<Nested {things} let:thing>
9+
<span>{thing.text}</span>
10+
</Nested>

0 commit comments

Comments
 (0)