Skip to content

Reactive statement re-run even though dependency (exported prop) didn't change #7129

Closed as not planned
@TylerRick

Description

@TylerRick

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?)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions