Lifecycle hooks
In Svelte 5, the component lifecycle consists of only two parts: Its creation and its destruction. Everything in-between - when certain state is updated - is not related to the component as a whole, only the parts that need to react to the state change are notified. This is because under the hood the smallest unit of change is actually not a component, it’s the (render) effects that the component sets up upon component initialization. Consequently, there’s no such thing as a “before update”/"after update” hook.
onMount
The onMount
function schedules a callback to run as soon as the component has been mounted to the DOM. It must be called during the component’s initialisation (but doesn’t need to live inside the component; it can be called from an external module).
onMount
does not run inside a component that is rendered on the server.
<script>
import { onMount } from 'svelte';
onMount(() => {
console.log('the component has mounted');
});
</script>
If a function is returned from onMount
, it will be called when the component is unmounted.
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
console.log('beep');
}, 1000);
return () => clearInterval(interval);
});
</script>
This behaviour will only work when the function passed to
onMount
synchronously returns a value.async
functions always return aPromise
, and as such cannot synchronously return a function.
onDestroy
Schedules a callback to run immediately before the component is unmounted.
Out of onMount
, beforeUpdate
, afterUpdate
and onDestroy
, this is the
only one that runs inside a server-side component.
function onDestroy(fn: () => any): void;
Schedules a callback to run immediately before the component is unmounted.
Out of onMount
, beforeUpdate
, afterUpdate
and onDestroy
, this is the only one that runs inside a server-side component.
<script>
import { onDestroy } from 'svelte';
onDestroy(() => {
console.log('the component is being destroyed');
});
</script>
tick
While there’s no “after update” hook, you can use tick
to ensure that the UI is updated before continuing. tick
returns a promise that resolves once any pending state changes have been applied, or in the next microtask if there are none.
<script>
import { beforeUpdate, tick } from 'svelte';
beforeUpdate(async () => {
console.log('the component is about to update');
await tick();
console.log('the component just updated');
});
</script>
Deprecated: beforeUpdate / afterUpdate
Svelte 4 contained hooks that ran before and after the component as a whole was updated. For backwards compatibility, these hooks were shimmed in Svelte 5 but not available inside components that use runes.
<script>
import { beforeUpdate, afterUpdate } from 'svelte';
beforeUpdate(() => {
console.log('the component is about to update');
});
afterUpdate(() => {
console.log('the component just updated');
});
</script>
Instead of beforeUpdate
use $effect.pre
and instead of afterUpdate
use $effect
instead - these runes offer more granular control and only react to the changes you’re actually interested in.
Chat window example
To implement a chat window that autoscrolls to the bottom when new messages appear (but only if you were already scrolled to the bottom), we need to measure the DOM before we update it.
In Svelte 4, we do this with beforeUpdate
, but this is a flawed approach — it fires before every update, whether it’s relevant or not. In the example below, we need to introduce checks like updatingMessages
to make sure we don’t mess with the scroll position when someone toggles dark mode.
With runes, we can use $effect.pre
, which behaves the same as $effect
but runs before the DOM is updated. As long as we explicitly reference messages
inside the effect body, it will run whenever messages
changes, but not when theme
changes.
beforeUpdate
, and its equally troublesome counterpart afterUpdate
, are therefore deprecated in Svelte 5.
<script>
import { beforeUpdate, afterUpdate, tick } from 'svelte';
let updatingMessages = false;
let theme = $state('dark');
let messages = $state([]);
let viewport;
beforeUpdate(() => {
$effect.pre(() => {
if (!updatingMessages) return;
messages;
const autoscroll = viewport && viewport.offsetHeight + viewport.scrollTop > viewport.scrollHeight - 50;
if (autoscroll) {
tick().then(() => {
viewport.scrollTo(0, viewport.scrollHeight);
});
}
updatingMessages = false;
});
function handleKeydown(event) {
if (event.key === 'Enter') {
const text = event.target.value;
if (!text) return;
updatingMessages = true;
messages = [...messages, text];
event.target.value = '';
}
}
function toggle() {
toggleValue = !toggleValue;
}
</script>
<div class:dark={theme === 'dark'}>
<div bind:this={viewport}>
{#each messages as message}
<p>{message}</p>
{/each}
</div>
<input onkeydown={handleKeydown} />
<button onclick={toggle}> Toggle dark mode </button>
</div>