Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Svelte 5: Reference to derived function does not trigger effect #13352

Closed
dnass opened this issue Sep 20, 2024 · 8 comments
Closed

Svelte 5: Reference to derived function does not trigger effect #13352

dnass opened this issue Sep 20, 2024 · 8 comments

Comments

@dnass
Copy link

dnass commented Sep 20, 2024

Describe the bug

In Svelte 4, it's possible to do:

<script>
  let n = 0;
  $: func = () => n;
  $: func, console.log('func changed'); // Effect runs when n changes
</script>

<button on:click={() => n++}>Click</button>

The runes equivalent does not work:

<script>
  let n = $state(0);
  let func = $derived(() => n);
  $effect(() => {
    func, console.log('func changed'); // Effect does not run when n changes
  });
</script>

<button onclick={() => n++}>Click</button>

If func is called within the $effect callback, the effect runs. When it is only referenced, the effect does not run.

Reproduction

https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAE32PwWrDMAyGX0WIQh0a2p3TJFD2GMsOnS0XU1cKsRIoIe8-vHTsMNqDDvr060Oa0YdICauPGfl8I6zw1PdYot773KSJohKWmGQcbCZ1skPote2400gKDA1skp6VzFtx_KV-ZJsHjoYwkTOmgKYFXgO5NuQ9WX0M5h-seasEK5wk0j7KxWwz0iAMLAqBJ7mS266apej4pcoUz2T_RfXh7y-uv0ZVYRC2MdhrMz_O3-2W9j2T-rAmWizxJi74QA4rHUZaPpdv_HowmFQBAAA=

Logs

No response

System Info

System:
    OS: macOS 14.7
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 361.81 MB / 16.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.13.1 - ~/.nvm/versions/node/v20.13.1/bin/node
    Yarn: 1.22.22 - ~/.nvm/versions/node/v20.13.1/bin/yarn
    npm: 10.5.2 - ~/.nvm/versions/node/v20.13.1/bin/npm
    pnpm: 9.11.0 - ~/.nvm/versions/node/v20.13.1/bin/pnpm
  Browsers:
    Brave Browser: 100.1.37.111
    Chrome: 128.0.6613.139
    Chrome Canary: 131.0.6728.0
    Safari: 18.0
  npmPackages:
    svelte: ^5.0.0-next.251 => 5.0.0-next.251

Severity

blocking an upgrade

@Conduitry
Copy link
Member

I think one question here is whether () => n ought to be considered to change when n changes (and thus, whether func should be considered to change), and I don't know the answer to that. I'm thinking no, because evaluating () => n doesn't involve evaluating n, and so the dependence isn't established.

@brunnerh
Copy link
Member

brunnerh commented Sep 20, 2024

In Svelte 5 you generally should not need this pattern of capturing variables in functions via $: declarations.

Dependencies are automatically tracked across function boundaries, so the function itself not changing should not matter for practical applications.

E.g. this does not work in Svelte 4 but just does in Svelte 5:

<script>
	let n = 1;
	function double() {
		return n * 2;
	}
</script>

<button on:click={() => n++}>{n}</button>

Double: {double()}

If you have a realistic use case where it does matter, please explain it.

@paoloricciuti
Copy link
Member

And if you really really need something like this you can use $derived.by

<script>
  let n = $state(0);
  let func = $derived.by(() => {
      n;
      return () => n;
   });
  $effect(() => {
    func, console.log('func changed'); // Effect does not run when n changes
  });
</script>

<button onclick={() => n++}>Click</button>

@Rich-Harris
Copy link
Member

As often, the question here is 'what are you actually trying to do?'

@dnass
Copy link
Author

dnass commented Sep 20, 2024

Maybe it's an outlier, but my use case does rely on triggering an effect when a function's dependencies change. In my svelte-canvas library, users define reactive functions that encapsulate pieces of canvas rendering logic. When any render function's dependencies change, the library clears the canvas and then reruns each render function. Here's a simple example in Svelte 4.

The render functions could just rerun with every rAF tick, but that would cause wasteful renders where nothing has changed. @paoloricciuti's suggestion works, but it's extra boilerplate for users. Either solution feels like a compromise compared with what's possible in Svelte 4.

@paoloricciuti
Copy link
Member

Maybe it's an outlier, but my use case does rely on triggering an effect when a function's dependencies change. In my svelte-canvas library, users define reactive functions that encapsulate pieces of canvas rendering logic. When any render function's dependencies change, the library clears the canvas and then reruns each render function. Here's a simple example in Svelte 4.

The render functions could just rerun with every rAF tick, but that would cause wasteful renders where nothing has changed. @paoloricciuti's suggestion works, but it's extra boilerplate for users. Either solution feels like a compromise compared with what's possible in Svelte 4.

I think you can change your library to just call the render function in an effect...by doing so every reactive variable inside the function will be registered as a dependency

@dnass
Copy link
Author

dnass commented Sep 20, 2024

The render functions run in a specific order. If the changed function is called in an effect, that will happen first, and then trigger every other function to re-render in order (including the changed function, at its actual position in the sequence). The upshot is that each changed render function would run twice per frame instead of once.

@paoloricciuti
Copy link
Member

The render functions run in a specific order. If the changed function is called in an effect, that will happen first, and then trigger every other function to re-render in order (including the changed function, at its actual position in the sequence). The upshot is that each changed render function would run twice per frame instead of once.

Maybe I'm missing something but something like this could work I think

I've used a set but you could just as well use an array if you want to insert in specific places. Basically the point is: you shouldn't use the effect to trigger the chained render but just call every render in an effect... everytime something changes the whole chain will be rerun

@sveltejs sveltejs locked and limited conversation to collaborators Sep 20, 2024
@Conduitry Conduitry converted this issue into discussion #13354 Sep 20, 2024

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants