Introduction
Runes
Template syntax
Styling
Special elements
Runtime
Misc
Reference
Legacy APIs
$effect
Effects are functions that run when state updates, and can be used for things like calling third-party libraries, drawing on<canvas>
elements, or making network requests. They only run in the browser, not during server-side rendering.
Generally speaking, you shouldnot update state inside effects, as it will make code more convoluted and will often lead to never-ending update cycles. If you find yourself doing so, seewhen not to use$effect
to learn about alternative approaches.
You can create an effect with the$effect
rune (demo):
<script>letsize=$state(50);letcolor=$state('#ff3e00');letcanvas;$effect(()=>{constcontext=canvas.getContext('2d');context.clearRect(0,0,canvas.width,canvas.height);// this will re-run whenever `color` or `size` changecontext.fillStyle=color;context.fillRect(0,0,size,size);});</script><canvasbind:this={canvas}width="100"height="100"></canvas>
When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed (unless accessed insideuntrack
), and re-runs the function when that state later changes.
If you’re having difficulty understanding why your
$effect
is rerunning or is not running seeunderstanding dependencies. Effects are triggered differently than the$:
blocks you may be used to if coming from Svelte 4.
Understanding lifecycle
Your effects run after the component has been mounted to the DOM, and in amicrotask after state changes. Re-runs are batched (i.e. changingcolor
andsize
in the same moment won’t cause two separate runs), and happen after any DOM updates have been applied.
You can use$effect
anywhere, not just at the top level of a component, as long as it is called while a parent effect is running.
Svelte uses effects internally to represent logic and expressions in your template — this is how
<h1>hello {name}!</h1>
updates whenname
changes.
An effect can return ateardown function which will run immediately before the effect re-runs (demo).
<script>letcount=$state(0);letmilliseconds=$state(1000);$effect(()=>{// This will be recreated whenever `milliseconds` changesconstinterval=setInterval(()=>{count+=1;},milliseconds);return()=>{// if a teardown function is provided, it will run// a) immediately before the effect re-runs// b) when the component is destroyedclearInterval(interval);};});</script><h1>{count}</h1><buttononclick={()=>(milliseconds*=2)}>slower</button><buttononclick={()=>(milliseconds/=2)}>faster</button>
Teardown functions also run when the effect is destroyed, which happens when its parent is destroyed (for example, a component is unmounted) or the parent effect re-runs.
Understanding dependencies
$effect
automatically picks up any reactive values ($state
,$derived
,$props
) that aresynchronously read inside its function body (including indirectly, via function calls) and registers them as dependencies. When those dependencies change, the$effect
schedules a re-run.
If$state
and$derived
are used directly inside the$effect
(for example, during creation of areactive class), those values willnot be treated as dependencies.
Values that are readasynchronously — after anawait
or inside asetTimeout
, for example — will not be tracked. Here, the canvas will be repainted whencolor
changes, but not whensize
changes (demo):
function$effect(fn:()=>void|(()=>void)):voidnamespace$effect
Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e.$state
or$derived
values.The timing of the execution is after the DOM has been updated.
Example:
$effect(()=>console.log('The count is now '+count));
If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.
Does not run during server side rendering.
@paramfn The function to execute$effect(()=>{constconstcontext:CanvasRenderingContext2D
context=letcanvas:{width:number;height:number;getContext(type:"2d",options?:CanvasRenderingContext2DSettings):CanvasRenderingContext2D;}
canvas.functiongetContext(type:"2d",options?:CanvasRenderingContext2DSettings):CanvasRenderingContext2D
getContext('2d');constcontext:CanvasRenderingContext2D
context.CanvasRect.clearRect(x: number,y: number,w: number,h: number):void
clearRect(0,0,letcanvas:{width:number;height:number;getContext(type:"2d",options?:CanvasRenderingContext2DSettings):CanvasRenderingContext2D;}
canvas.width:number
width,letcanvas:{width:number;height:number;getContext(type:"2d",options?:CanvasRenderingContext2DSettings):CanvasRenderingContext2D;}
canvas.height:number
height);// this will re-run whenever `color` changes...constcontext:CanvasRenderingContext2D
context.CanvasFillStrokeStyles.fillStyle: string|CanvasGradient|CanvasPattern
fillStyle=letcolor:string
color;functionsetTimeout<[]>(callback:()=>void,delay?:number):NodeJS.Timeout(+2overloads)
Schedules execution of a one-timecallback
afterdelay
milliseconds.
Thecallback
will likely not be invoked in preciselydelay
milliseconds.Node.js makes no guarantees about the exact timing of when callbacks will fire,nor of their ordering. The callback will be called as close as possible to thetime specified.
Whendelay
is larger than2147483647
or less than1
orNaN
, thedelay
will be set to1
. Non-integer delays are truncated to an integer.
Ifcallback
is not a function, aTypeError
will be thrown.
This method has a custom variant for promises that is available usingtimersPromises.setTimeout()
.
@sincev0.0.1@paramcallback The function to call when the timer elapses.@paramdelay The number of milliseconds to wait before calling thecallback
.Default:1
.@paramargs Optional arguments to pass when thecallback
is called.@returnsfor use withclearTimeout()
setTimeout(()=>{// ...but not when `size` changesconstcontext:CanvasRenderingContext2D
context.CanvasRect.fillRect(x: number,y: number,w: number,h: number):void
fillRect(0,0,letsize:number
size,letsize:number
size);},0);});
An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changesinside an object at dev time, you can use$inspect
.)
<script>letstate=$state({ value:0});letderived=$derived({ value:state.value*2});// this will run once, because `state` is never reassigned (only mutated)$effect(()=>{state;});// this will run whenever `state.value` changes...$effect(()=>{state.value;});// ...and so will this, because `derived` is a new object each time$effect(()=>{derived;});</script><buttononclick={()=>(state.value+=1)}>{state.value}</button><p>{state.value} doubled is {derived.value}</p>
An effect only depends on the values that it read the last time it ran. This has interesting implications for effects that have conditional code.
For instance, ifcondition
istrue
in the code snippet below, the code inside theif
block will run andcolor
will be evaluated. As such, changes to eithercondition
orcolor
will cause the effect to re-run.
Conversely, ifcondition
isfalse
,color
will not be evaluated, and the effect willonly re-run again whencondition
changes.
importfunctionconfetti(opts?:ConfettiOptions):void
confettifrom'canvas-confetti';letletcondition:boolean
condition=function$state<true>(initial:true):true(+1overload)namespace$state
@paraminitial The initial value$state(true);letletcolor:string
color=function$state<"#ff3e00">(initial:"#ff3e00"):"#ff3e00"(+1overload)namespace$state
@paraminitial The initial value$state('#ff3e00');function$effect(fn:()=>void|(()=>void)):voidnamespace$effect
Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e.$state
or$derived
values.The timing of the execution is after the DOM has been updated.
Example:
$effect(()=>console.log('The count is now '+count));
If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.
Does not run during server side rendering.
@paramfn The function to execute$effect(()=>{if(letcondition:true
condition) {functionconfetti(opts?:ConfettiOptions):void
confetti({ConfettiOptions.colors: string[]
colors:[letcolor:string
color] });}else{functionconfetti(opts?:ConfettiOptions):void
confetti();}});
$effect.pre
In rare cases, you may need to run codebefore the DOM updates. For this we can use the$effect.pre
rune:
<script>import{ tick }from'svelte';letdiv=$state();letmessages=$state([]);// ...$effect.pre(()=>{if(!div)return;// not yet mounted// reference `messages` array length so that this code re-runs whenever it changesmessages.length;// autoscroll when new messages are addedif(div.offsetHeight+div.scrollTop>div.scrollHeight-20) {tick().then(()=>{div.scrollTo(0,div.scrollHeight);});}});</script><divbind:this={div}>{#eachmessagesasmessage}<p>{message}</p>{/each}</div>
Apart from the timing,$effect.pre
works exactly like$effect
.
$effect.tracking
The$effect.tracking
rune is an advanced feature that tells you whether or not the code is running inside a tracking context, such as an effect or inside your template (demo):
<script>console.log('in component setup:',$effect.tracking());// false$effect(()=>{console.log('in effect:',$effect.tracking());// true});</script><p>in template: {$effect.tracking()}</p><!-- true -->
It is used to implement abstractions likecreateSubscriber
, which will create listeners to update reactive values butonly if those values are being tracked (rather than, for example, read inside an event handler).
$effect.pending
When usingawait
in components, the$effect.pending()
rune tells you how many promises are pending in the currentboundary, not including child boundaries (demo):
<buttononclick={()=>a++}>a++</button><buttononclick={()=>b++}>b++</button><p>{a} + {b} = {awaitadd(a,b)}</p>{#if$effect.pending()}<p>pending promises: {$effect.pending()}</p>{/if}
$effect.root
The$effect.root
rune is an advanced feature that creates a non-tracked scope that doesn’t auto-cleanup. This is useful for nested effects that you want to manually control. This rune also allows for the creation of effects outside of the component initialisation phase.
constconstdestroy:()=>void
destroy=namespace$effectfunction$effect(fn:()=>void|(()=>void)):void
Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e.$state
or$derived
values.The timing of the execution is after the DOM has been updated.
Example:
$effect(()=>console.log('The count is now '+count));
If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.
Does not run during server side rendering.
@paramfn The function to execute$effect.function$effect.root(fn:()=>void|(()=>void)):()=>void
The$effect.root
rune is an advanced feature that creates a non-tracked scope that doesn’t auto-cleanup. This is useful fornested effects that you want to manually control. This rune also allows for creation of effects outside of the componentinitialisation phase.
Example:
<script>let count = $state(0);const cleanup = $effect.root(() => {$effect(()=>{console.log(count);})return()=>{console.log('effect root cleanup');}});</script><button onclick={()=>cleanup()}>cleanup</button>
root(()=>{function$effect(fn:()=>void|(()=>void)):voidnamespace$effect
Runs code when a component is mounted to the DOM, and then whenever its dependencies change, i.e.$state
or$derived
values.The timing of the execution is after the DOM has been updated.
Example:
$effect(()=>console.log('The count is now '+count));
If you return a function from the effect, it will be called right before the effect is run again, or when the component is unmounted.
Does not run during server side rendering.
@paramfn The function to execute$effect(()=>{// setup});return()=>{// cleanup};});// later...constdestroy:()=>void
destroy();
When not to use $effect
In general,$effect
is best considered something of an escape hatch — useful for things like analytics and direct DOM manipulation — rather than a tool you should use frequently. In particular, avoid using it to synchronise state. Instead of this...
<script>letcount=$state(0);letdoubled=$state();// don't do this!$effect(()=>{doubled=count*2;});</script>
...do this:
<script>letcount=$state(0);letdoubled=$derived(count*2);</script>
For things that are more complicated than a simple expression like
count * 2
, you can also use$derived.by
.
If you’re using an effect because you want to be able to reassign the derived value (to build an optimistic UI, for example) note thatderiveds can be directly overridden as of Svelte 5.25.
You might be tempted to do something convoluted with effects to link one value to another. The following example shows two inputs for “money spent” and “money left” that are connected to each other. If you update one, the other should update accordingly. Don’t use effects for this (demo):
<script>consttotal=100;letspent=$state(0);letleft=$state(total);$effect(()=>{left=total-spent;});$effect(()=>{spent=total-left;});</script><label><inputtype="range"bind:value={spent}max={total} />{spent}/{total} spent</label><label><inputtype="range"bind:value={left}max={total} />{left}/{total} left</label>
Instead, useoninput
callbacks or — better still —function bindings where possible (demo):
<script>consttotal=100;letspent=$state(0);letleft=$derived(total-spent);functionupdateLeft(left) {spent=total-left;}</script><label><inputtype="range"bind:value={spent}max={total} />{spent}/{total} spent</label><label><inputtype="range"bind:value={()=>left,updateLeft}max={total} />{left}/{total} left</label>
If you absolutely have to update$state
within an effect and run into an infinite loop because you read and write to the same$state
, useuntrack.