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.
pull/10188/head
Simon H 10 months ago committed by GitHub
parent b94d72bbfb
commit db8cba3216
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
"svelte": patch
---
fix: react to mutated slot props in legacy mode

@ -2886,7 +2886,12 @@ export const template_visitors = {
const name = node.expression === null ? node.name : node.expression.name;
return b.const(
name,
b.call('$.derived', b.thunk(b.member(b.id('$$slotProps'), b.id(node.name))))
b.call(
// in legacy mode, sources can be mutated but they're not fine-grained.
// Using the safe-equal derived version ensures the slot is still updated
state.analysis.runes ? '$.derived' : '$.derived_safe_equal',
b.thunk(b.member(b.id('$$slotProps'), b.id(node.name)))
)
);
}
},

@ -1307,6 +1307,18 @@ export function derived(init) {
return signal;
}
/**
* @template V
* @param {() => V} init
* @returns {import('./types.js').ComputationSignal<V>}
*/
/*#__NO_SIDE_EFFECTS__*/
export function derived_safe_equal(init) {
const signal = derived(init);
signal.e = safe_equal;
return signal;
}
/**
* @template V
* @param {V} initial_value

@ -7,6 +7,7 @@ export {
source,
mutable_source,
derived,
derived_safe_equal,
prop,
user_effect,
render_effect,

@ -0,0 +1,9 @@
<script>
export let things;
</script>
<div>
{#each things as thing}
<slot {thing}/>
{/each}
</div>

@ -0,0 +1,25 @@
import { tick } from 'svelte';
import { test } from '../../test';
export default test({
html: `
<button>mutate</button>
<div>
<span>hello</span>
</div>
`,
async test({ assert, target }) {
target.querySelector('button')?.click();
await tick();
assert.htmlEqual(
target.innerHTML,
`
<button>mutate</button>
<div>
<span>bye</span>
</div>
`
);
}
});

@ -0,0 +1,10 @@
<script>
import Nested from './Nested.svelte';
let things = [{ text: 'hello' }];
</script>
<button on:click={() => things[0].text = 'bye'}>mutate</button>
<Nested {things} let:thing>
<span>{thing.text}</span>
</Nested>
Loading…
Cancel
Save