Skip to main content

So far we’ve talked about reactivity in terms of state. But that’s only half of the equation — state is only reactive if something is reacting to it, otherwise it’s just a sparkling variable.

The thing that reacts is called an effect. You’ve already encountered effects — the ones that Svelte creates on your behalf to update the DOM in response to state changes — but you can also create your own with the $effect rune.

Most of the time, you shouldn’t. $effect is best thought of as an escape hatch, rather than something to use frequently. If you can put your side effects in an event handler, for example, that’s almost always preferable.

Let’s say we want to use setInterval to keep track of how long the component has been mounted. Create the effect:

App
<script>
	let elapsed = $state(0);
	let interval = $state(1000);

	$effect(() => {
		setInterval(() => {
			elapsed += 1;
		}, interval);
	});
</script>

Click the ‘speed up’ button a few times and notice that elapsed ticks up faster, because we’re calling setInterval each time interval gets smaller.

If we then click the ‘slow down’ button... well, it doesn’t work. That’s because we’re not clearing out the old intervals when the effect updates. We can fix that by returning a cleanup function:

App
$effect(() => {
	const id = setInterval(() => {
		elapsed += 1;
	}, interval);

	return () => {
		clearInterval(id);
	};
});

The cleanup function is called immediately before the effect function re-runs when interval changes, and also when the component is destroyed.

If the effect function doesn’t read any state when it runs, it will only run once, when the component mounts.

Effects do not run during server-side rendering.

Edit this page on GitHub

1
2
3
4
5
6
7
8
9
10
<script>
	let elapsed = $state(0);
	let interval = $state(1000);
</script>
 
<button onclick={() => interval /= 2}>speed up</button>
<button onclick={() => interval *= 2}>slow down</button>
 
<p>elapsed: {elapsed}</p>