Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Should component props be reference-deduped the same way $derived values are?#16344

Avaq started this conversation inIdeas
Discussion options

I keep running into the same issue with Svelte 5, and that's that my$effects are running too often, when they depend on a component property. Let me show an example:

<script>const {emailAddress,sessionDuration }=$props();$effect(()=> {console.log('Sending an email to', emailAddress);// sendEmail(emailAddress, 'Email address updated successfully!');  });</script><h1>Hello!</h1><p>You've been active for {sessionDuration} seconds!</p>

In the parent component, we do something like this:

<script>import {onMount}from'svelte';importChildfrom'./Child.svelte';let sessionDuration=$state(0);let emailAddress=$state('');onMount(()=> {setInterval(()=> sessionDuration++,1000);  });// I know this is contrived, but it's not unusual for me to end up with// a single large reactive "root state" from which different props are// picked and sent into child components.conststate=$derived({sessionDuration, emailAddress});</script><inputtype="email"bind:value={emailAddress} /><ChildemailAddress={state.emailAddress}sessionDuration={state.sessionDuration}/>

This code has a bug where it sends an email every second, not just when the email address changes. This happensdespiteemailAddress being the only dependency of the$effect.

Why? Because the parent component is rerenderingChild every timestate changes, and thistriggers reactivity on all props in the child. To fix it, the child can wrap$derived around each of its props (that are going to be used as a dependency of an$effect):

 <script>-  const { emailAddress, sessionDuration } = $props();+  const props = $props();+  const emailAddress = $derived(props.emailAddress);+  const sessionDuration = $derived(props.sessionDuration);    $effect(() => {     console.log('Sending an email to', emailAddress);     // sendEmail(emailAddress, 'Email address updated successfully!');   }); </script> <h1>Hello!</h1> <p>You've been active for {sessionDuration} seconds!</p>

The$derived rune deduplicates reactions based on the reference equality of its computed result. Now the$effect below stops reacting to an update ofprops, and only still reacts to an update toemailAddress, like we expect.


To me, this feels very unintuitive, and it's annoying to have to write the extra boilerplate every time I want to create an$effect that reacts to a component property.

I think it'd be much more intuitive if the same deduplication logic that happens at the boundaries of a$derived,also happen at the boundaries of individual component properties.

What do you think?

You must be logged in to vote

Replies: 1 comment 1 reply

Comment options

This is a classic example of something that shouldn't have been an effect in the first place! Effects are about reacting to statebeing a certain way, not to statechanging in a certain way. If you want to react to changes, use a callback (which could be applied as an event listener or a function binding or whatever).

If you apply this mindset when thinking about how data flows through your app, you will free yourself of anxiety around when effects re-run. Effects running more or less frequently is a question of optimisation, not semantics.

If we always added deduplication at prop boundaries, it would benefit this one specific case at the cost of others.

You must be logged in to vote
1 reply
@Avaq
Comment options

Thanks for your reply! So what you're saying is thatin general, effects should be allowed to run whenever?

Taken to the extreme: If the Svelte team decided that all effects will, from now on, run on a 3 second timer in addition to in response to their dependencies changing, then thatshould not cause any problems. And any user code breaking from such a change would have been following thereact-to-changes anti-pattern.

It sounds quite reasonable to me in theory. Let me try to incorporate that line of thinking into practice. :)

My email example may have been badly chosen. Of course I just made it up on the spot to illustrate the problem. So I did a search through our code base to find out how we really use$effect, and I think that in 90% of cases, our code would break if the effects would run more often than only in response to changing dependencies.

However, most of the first 20 usages seem like they could be easily refactored into$derived state that the component also writes to, because typically these effects are just used to sync some state. I think this rampant abuse of$effect throughout our code base has definitely made this perceived problem seem worse than it really is.

In some cases though, the effects are used to trigger data loading, to start timers, or stuff like that. I'm not sure how these should be rewritten to avoid$effect. Sure, loading the data too often is "just not optimal" and loading it exactly as often as needed is "just an optimization", but it's the kind of optimal I'd expect from the onset 😅

Finally, I didn't quite understand what you meant by "using a callback" as an alternative to using an effect. The way I see it, callbacks are a way for a child component to send eventsup the tree, while reactive props are a way to send change-eventsdown the tree. How would callbacks achieve the latter?

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Ideas
Labels
None yet
2 participants
@Avaq@Rich-Harris

[8]ページ先頭

©2009-2025 Movatter.jp