sveltesveltekitsvelte-4

Statement rerun unnecessarily


I have some strange things happening on my svelte code, here are the simplified version of my code

REPL: https://codesandbox.io/p/devbox/hcfk3l

+page.svelte

<script lang="ts">
  import { writable } from 'svelte/store'

  import Provider from './provider.svelte'

  import Test from './test.svelte'

  const sdk = writable<object>({})
  sdk.subscribe((v) => console.log('sdk updated:', v))
</script>

<div class="w-full mx-auto max-w-xl">
  <h2 class="text-center text-gray-30 uppercase text-c2">Transfer</h2>
  <Provider let:values let:val>
    <form class="flex flex-col gap-4">
      <button
        on:click={() => {
          console.log('update balance')
          values.update((v) => {
            return { ...v, balance: 0 }
          })
        }}>update balance</button>
      <button
        on:click={() => {
          sdk.set({})
        }}>update sdk</button>
      <Test
        func={async () => {
          await new Promise((resolve) => setTimeout(resolve, 1500))
          console.log('update balance')
          values.update((v) => {
            return { ...v, balance: 0 }
          })
        }}
        sdk={$sdk} />
    </form>
  </Provider>
</div>

provider.svelte

<script lang="ts">
  import { writable } from 'svelte/store'

  const values = writable({})
</script>

<slot {values} val={$values} />

test.svelte

<script lang="ts">
  import { onDestroy } from 'svelte'

  export let sdk: any
  export let func: () => Promise<any>
  $: console.log('sdk TEST!', sdk)
  $: {
    if (sdk) {
      func()
    }
  }

  onDestroy(() => {
    console.log('destroyed')
  })
</script>

Can anyone tell me why when I click on the update sdk button, the calls flow:

  1. sdk updated
  2. sdk TEST!
  3. update balance
  4. sdk TEST!
  5. update balance ...

it is weird because the sdk TEST! is getting called after the update balance, because in update balance, there is no change to sdk, even the sdk updated is not getting called.

when I try to call the update balance using the other button (update balance button), it doesn't call the sdk TEST! after the balance update

so why the update balance call from the func makes sdk TEST! getting called, why the update balance from the button doesn't?


Solution

  • And round and round it goes xD It's a bit hard for me to follow the code execution in my head, but I think the problem is the func prop in the <Test> component. When the function in the func prop is called, then you change the values store, and I that causes the <Provider> child to re-render, because in the <Provider> component you pass its children the prop val={$values}. So, when values is a assigned a new value:

    1. The <Provider> component will re-render its child
    2. That child will re-render <Test func={...} />
    3. When <Test> is re-rendered, it will change the value in values
    4. Which causes a re-render of (1), and in the merry-go-round we are! :D

    I think you can solve the problem by simply skipping the val prop in the provider.svelte component. That's the one that causes an unnecessary re-rendering, because you don't use it anyway.