Description
Describe the bug
See my repro (based on actual problem I ran into):
https://svelte.dev/repl/ff6e69e975df44f3821cc4ed956881f8
<script>
export let filters;
export function fetchItems(filters) {
return [{name: 'B'}, {name: 'A'}]
}
let items = []
$: console.log('filters changed?', filters)
$: {
items = fetchItems(filters)
console.log('re-fetched, overwriting sorted array with unsorted array')
}
$: console.log('items is now:', items.map(el => el.name))
function handleSort() {
items.sort((a,b) => -1)
console.log('items is now sorted:', items.map(el => el.name))
items = items
}
</script>
{#each items as item}
<ol>
<li>{item.name}</li>
</ol>
{/each}
<button on:click={handleSort}>Sort!</button>
Expected behavior: When handleSort is called, it should sort items
, and render the updated list with [A, B]. Because the $: items = fetchItems(filters)
block depends only on filters
and filters
has not changed, it should not be re-run.
Actual behavior: When handleSort updates items = items
, it re-runs the $: items = fetchItems(filters)
block (why???), causing an extra unnecessary fetch
, and worse, causing the results of the sort to be lost and replaced with unsorted list, [B, A].
You can see from the console that after it sorts the items, it erroneously triggers both of these to re-run, even though filters
has not — and could not have — changed.
$: console.log('filters changed?', filters)
$: items = fetchItems(filters)
Similar issues
Looks similar to Test 1 from https://svelte.dev/repl/58570a9e05a240f591a76b4eeab09598?version=3.46.1 (#5731).
Just like in that issue, I only expect my reactive statement to be triggered (re-fetch data based on filters) if the exported filters
prop changes.
But unlike in that issue, where it was put forth (but not confirmed that this was the reason) that
Seems, Svelte sees these two statements together and decide that model depending on selected assignment.
$: if (model) { <-- this selected = []; <-- and this in the same expression }After that, when Svelte finds any selected assignments it will invalidates the model as well.
, I didn't see anything quite the same in my example.
Looks similar to #7045 . As in that case, if I run my repro on Svelte 3.2.0 (https://svelte.dev/repl/ff6e69e975df44f3821cc4ed956881f8?version=3.2.0) pre-#2444, it works as intended.
And of course it looks similar to #4933, which seems to be the canonical issue for all bugs like this... but is also too broad and confusing and possibly out-of-date (the linked-to REPL doesn't actually reproduce any bug). I'm guessing my issue would fall under Condition 1 of #4933?
Is the issue like @Rich-Harris said in https://stackoverflow.com/questions/61730267/reactive-statements-triggered-too-often, that
Objects are treated differently from primitives meaning Svelte will assume that they could have been mutated
So I'm guessing somehow it invalidates "too much" because it pessimistically assumes that filters
may have changed? —
Even though it seems abundantly clear from any possible static analysis of my repro code that filters
cannot have changed simply by triggering handleSort
. The only way for filters
to have changed is if the exported prop changed, and it didn't. (So how do I prevent it from thinking it did?)