$effect
Effects are what make your application do things. When Svelte runs an effect function, it tracks which pieces of state (and derived state) are accessed, and re-runs the function when that state later changes.
Most of the effects in a Svelte app are created by Svelte itself — they’re the bits that update the text in <h1>hello {name}!</h1>
when name
changes, for example.
But you can also create your own effects with the $effect
rune, which is useful when you need to synchronize an external system (whether that’s a library, or a <canvas>
element, or something across a network) with state inside your Svelte app.
Avoid overusing effects! When you do too much work in them, code often becomes difficult to understand and maintain. See when not to use effects to learn about alternative approaches.
Your effects run after the component has been mounted to the DOM, and in a microtask after state changes (demo):
<script>
let size = $state(50);
let color = $state('#ff3e00');
let canvas;
$effect(() => {
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
// this will re-run whenever `color` or `size` change
context.fillStyle = color;
context.fillRect(0, 0, size, size);
});
</script>
<canvas bind:this={canvas} width="100" height="100" />
Re-runs are batched (i.e. changing color
and size
in the same moment won’t cause two separate runs), and happen after any DOM updates have been applied.
You can place $effect
anywhere, not just at the top level of a component, as long as it is called during component initialization (or while a parent effect is active). It is then tied to the lifecycle of the component (or parent effect) and will therefore destroy itself when the component unmounts (or the parent effect is destroyed).
You can return a function from $effect
, which will run immediately before the effect re-runs, and before it is destroyed (demo).
<script>
let count = $state(0);
let milliseconds = $state(1000);
$effect(() => {
// This will be recreated whenever `milliseconds` changes
const interval = setInterval(() => {
count += 1;
}, milliseconds);
return () => {
// if a callback is provided, it will run
// a) immediately before the effect re-runs
// b) when the component is destroyed
clearInterval(interval);
};
});
</script>
<h1>{count}</h1>
<button onclick={() => (milliseconds *= 2)}>slower</button>
<button onclick={() => (milliseconds /= 2)}>faster</button>
Understanding dependencies
$effect
automatically picks up any reactive values ($state
, $derived
, $props
) that are synchronously read inside its function body and registers them as dependencies. When those dependencies change, the $effect
schedules a rerun.
Values that are read asynchronously — after an await
or inside a setTimeout
, for example — will not be tracked. Here, the canvas will be repainted when color
changes, but not when size
changes (demo):
function $effect(fn: () => void | (() => void)): void
namespace $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.
$effect(() => {
const const context: CanvasRenderingContext2D
context = let canvas: {
width: number;
height: number;
getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas.function getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D
getContext('2d');
const context: CanvasRenderingContext2D
context.CanvasRect.clearRect(x: number, y: number, w: number, h: number): void
clearRect(0, 0, let canvas: {
width: number;
height: number;
getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas.width: number
width, let canvas: {
width: number;
height: number;
getContext(type: "2d", options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D;
}
canvas.height: number
height);
// this will re-run whenever `color` changes...
const context: CanvasRenderingContext2D
context.CanvasFillStrokeStyles.fillStyle: string | CanvasGradient | CanvasPattern
fillStyle = let color: string
color;
function setTimeout<[]>(callback: () => void, ms?: number): NodeJS.Timeout (+2 overloads)
Schedules execution of a one-time callback
after delay
milliseconds.
The callback
will likely not be invoked in precisely delay
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 the
time specified.
When delay
is larger than 2147483647
or less than 1
, the delay
will be set to 1
. Non-integer delays are truncated to an integer.
If callback
is not a function, a TypeError
will be thrown.
This method has a custom variant for promises that is available using timersPromises.setTimeout()
.
setTimeout(() => {
// ...but not when `size` changes
const context: CanvasRenderingContext2D
context.CanvasRect.fillRect(x: number, y: number, w: number, h: number): void
fillRect(0, 0, let size: number
size, let size: 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 changes inside an object at dev time, you can use $inspect
.)
<script>
let state = $state({ value: 0 });
let derived = $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>
<button onclick={() => (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. If a
is true, changes to b
will not cause this effect to rerun:
function $effect(fn: () => void | (() => void)): void
namespace $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.
$effect(() => {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log('running');
if (let a: false
a || let b: false
b) {
var console: Console
The console
module provides a simple debugging console that is similar to the
JavaScript console mechanism provided by web browsers.
The module exports two specific components:
- A
Console
class with methods such as console.log()
, console.error()
and console.warn()
that can be used to write to any Node.js stream.
- A global
console
instance configured to write to process.stdout
and
process.stderr
. The global console
can be used without calling require('console')
.
Warning: The global console object’s methods are neither consistently
synchronous like the browser APIs they resemble, nor are they consistently
asynchronous like all other Node.js streams. See the note on process I/O
for
more information.
Example using the global console
:
console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr
Example using the Console
class:
const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err
console.Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)
Prints to stdout
with newline. Multiple arguments can be passed, with the
first used as the primary message and all additional used as substitution
values similar to printf(3)
(the arguments are all passed to util.format()
).
const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout
See util.format()
for more information.
log('inside if block');
}
});
$effect.pre
In rare cases, you may need to run code before the DOM updates. For this we can use the $effect.pre
rune:
<script>
import { tick } from 'svelte';
let div = $state();
let messages = $state([]);
// ...
$effect.pre(() => {
if (!div) return; // not yet mounted
// reference `messages` array length so that this code re-runs whenever it changes
messages.length;
// autoscroll when new messages are added
if (div.offsetHeight + div.scrollTop > div.scrollHeight - 20) {
tick().then(() => {
div.scrollTo(0, div.scrollHeight);
});
}
});
</script>
<div bind:this={div}>
{#each messages as message}
<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 -->
This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects. Here’s a readable
function that listens to changes from a callback function as long as it’s inside a tracking context:
import { function tick(): Promise<void>
Returns a promise that resolves once any pending state changes have been applied.
tick } from 'svelte';
export default function function readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
readonly value: T;
}
readable<function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
readonly value: T;
}
T>(
initial_value: T
initial_value: function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
readonly value: T;
}
T,
start: (callback: (update: (v: T) => T) => T) => () => void
start: (callback: (update: (v: T) => T) => T
callback: (update: (v: T) => T
update: (v: T
v: function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
readonly value: T;
}
T) => function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
readonly value: T;
}
T) => function (type parameter) T in readable<T>(initial_value: T, start: (callback: (update: (v: T) => T) => T) => () => void): {
readonly value: T;
}
T) => () => void
) {
let let value: T
value = function $state<T>(initial: T): T (+1 overload)
namespace $state
Declares reactive state.
Example:
let count = $state(0);
$state(initial_value: T
initial_value);
let let subscribers: number
subscribers = 0;
let let stop: (() => void) | null
stop: null | (() => void) = null;
return {
get value: T
value() {
// If in a tracking context ...
if (namespace $effect
function $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.
$effect.function $effect.tracking(): boolean
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.
Example:
<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 -->
This allows you to (for example) add things like subscriptions without causing memory leaks, by putting them in child effects.
https://svelte-5-preview.vercel.app/docs/runes#$effect-tracking
tracking()) {
function $effect(fn: () => void | (() => void)): void
namespace $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.
$effect(() => {
// ...and there's no subscribers yet...
if (let subscribers: number
subscribers === 0) {
// ...invoke the function and listen to changes to update state
let stop: (() => void) | null
stop = start: (callback: (update: (v: T) => T) => T) => () => void
start((fn: (v: T) => T
fn) => (let value: T
value = fn: (v: T) => T
fn(let value: T
value)));
}
let subscribers: number
subscribers++;
// The return callback is called once a listener unlistens
return () => {
function tick(): Promise<void>
Returns a promise that resolves once any pending state changes have been applied.
tick().Promise<void>.then<void, never>(onfulfilled?: ((value: void) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>
Attaches callbacks for the resolution and/or rejection of the Promise.
then(() => {
let subscribers: number
subscribers--;
// If it was the last subscriber...
if (let subscribers: number
subscribers === 0) {
// ...stop listening to changes
let stop: (() => void) | null
stop?.();
let stop: (() => void) | null
stop = null;
}
});
};
});
}
return let value: T
value;
}
};
}
$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 creation of effects outside of the component initialisation phase.
<script>
let count = $state(0);
const cleanup = $effect.root(() => {
$effect(() => {
console.log(count);
});
return () => {
console.log('effect root cleanup');
};
});
</script>
When not to use effects
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>
let count = $state(0);
let doubled = $state();
// don't do this!
$effect(() => {
doubled = count * 2;
});
</script>
...do this:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
For things that are more complicated than a simple expression like
count * 2
, you can also use$derived.by
.
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>
let total = 100;
let spent = $state(0);
let left = $state(total);
$effect(() => {
left = total - spent;
});
$effect(() => {
spent = total - left;
});
</script>
<label>
<input type="range" bind:value={spent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={left} max={total} />
{left}/{total} left
</label>
Instead, use callbacks where possible (demo):
<script>
let total = 100;
let spent = $state(0);
let left = $state(total);
function updateSpent(e) {
spent = +e.target.value;
left = total - spent;
}
function updateLeft(e) {
left = +e.target.value;
spent = total - left;
}
</script>
<label>
<input type="range" value={spent} oninput={updateSpent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" value={left} oninput={updateLeft} max={total} />
{left}/{total} left
</label>
If you need to use bindings, for whatever reason (for example when you want some kind of “writable $derived
“), consider using getters and setters to synchronise state (demo):
<script>
let total = 100;
let spent = $state(0);
let left = {
get value() {
return total - spent;
},
set value(v) {
spent = total - v;
}
};
</script>
<label>
<input type="range" bind:value={spent} max={total} />
{spent}/{total} spent
</label>
<label>
<input type="range" bind:value={left.value} max={total} />
{left.value}/{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
, use untrack.