mirror of https://github.com/sveltejs/svelte
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							168 lines
						
					
					
						
							7.7 KiB
						
					
					
				
			
		
		
	
	
							168 lines
						
					
					
						
							7.7 KiB
						
					
					
				| ---
 | |
| title: Lifecycle hooks
 | |
| ---
 | |
| 
 | |
| <!-- - onMount/onDestroy
 | |
| - mention that `$effect` might be better for your use case
 | |
| - beforeUpdate/afterUpdate with deprecation notice?
 | |
| - or skip this entirely and only have it in the reference docs? -->
 | |
| 
 | |
| 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.
 | |
| 
 | |
| ```svelte
 | |
| <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.
 | |
| 
 | |
| ```svelte
 | |
| <script>
 | |
| 	import { onMount } from 'svelte';
 | |
| 
 | |
| 	onMount(() => {
 | |
| 		const interval = setInterval(() => {
 | |
| 			console.log('beep');
 | |
| 		}, 1000);
 | |
| 
 | |
| 		return () => clearInterval(interval);
 | |
| 	});
 | |
| </script>
 | |
| ```
 | |
| 
 | |
| > [!NOTE] This behaviour will only work when the function passed to `onMount` _synchronously_ returns a value. `async` functions always return a `Promise`, and as such cannot _synchronously_ return a function.
 | |
| 
 | |
| ## `onDestroy`
 | |
| 
 | |
| > EXPORT_SNIPPET: svelte#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.
 | |
| 
 | |
| ```svelte
 | |
| <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.
 | |
| 
 | |
| ```svelte
 | |
| <script>
 | |
| 	import { tick } from 'svelte';
 | |
| 
 | |
| 	$effect.pre(() => {
 | |
| 		console.log('the component is about to update');
 | |
| 		tick().then(() => {
 | |
| 				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.
 | |
| 
 | |
| ```svelte
 | |
| <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.
 | |
| 
 | |
| - [Before](/playground/untitled#H4sIAAAAAAAAE31WXa_bNgz9K6yL1QmWOLlrC-w6H8MeBgwY9tY9NfdBtmlbiywZkpyPBfnvo2zLcZK28AWuRPGI5OGhkEuQc4EmiL9eAskqDOLg97oOZoE9125jDigs0t6oRqfOsjap5rXd7uTO8qpW2sIFEsyVxn_qjFmcAcstar-xPN3DFXKtKgi768IVgQku0ELj3Lgs_kZjWIEGNpAzYXDlHWyJFZI1zJjeh4O5uvl_DY8oUkVeVoFuJKYls-_CGYS25Aboj0EtWNqel0wWoBoLTGZgmdgDS9zW4Uz4NsrswPHoyutN4xInkylstnBxdmIhh8m7xzqmoNE2Wq46n1RJQzEbq4g-JQSl7e-HDx-GdaTy3KD9E3lRWvj5Zu9QX1QN20dj7zyHz8s-1S6lW7Cpz3RnXTcm04hIlfdFuO8p2mQ5-3a06cqjrn559bF_2NHOnRZ5I1PLlXQNyQT-hedMHeUEDyjtdMxsa4n2eIbNhlTwhyRthaOKOmYtniwF6pwt0wXa6MBEg0OibZec27gz_dk3UrZ6hB2LLYoiv521Yd8Gt-foTrfhiCDP0lC9VUUhcDLU49Xe_9943cNvEArHfAjxeBTovvXiNpFynfEDpIIZs9kFbg52QbeNHWZzebz32s7xHco3nJAJl1nshmhz8dYOQJDyZetnbb2gTWe-vEeWlrfpZMavr56ldb29eNt6UXvgwgFbp_WC0tl2RK25rGk6lYz3nUI2lzvBXGHhPZPGWmKUXFNBKqdaW259wl_aHbiqoVIZdpE60Nax6IOujT0LbFFxIVTCxCRR2XloUcYNvSbnGHKBp763jHoj59xiZWJI0Wm0P_m3MSS985xkasn-cFq20xTDy3J5KFcjgUTD69BHdcHIjz431z28IqlxGcPSfdFnrGDZn6gD6lyo45zyHAD-btczf-98nhQxHEvKfeUtOVkSejD3q-9X7JbzjGtsdUxlKdFU8qGsT78uaw848syWMXz85Waq2Gnem4mAn3prweq4q6Y3JEpnqMmnPoFRgmd3ySW0LLRqSKlwYHriCvJvUs2yjMaaoA-XzTXLeGMe45zmhv_XAno3Mj0xF7USuqNvnE9H343QHlq-eAgxpbTPNR9yzUkgLjwSR0NK4wKoxy-jDg-9vy8sUSToakzW-9fX13Em9Q8T6Z26uZhBN36XUYo5q7ggLXBZoub2Ofv7g6GCZfTxe034NCjiudXj7Omla0eTfo7QBPOcYxbE7qG-vl3_B1G-_i_JCAAA)
 | |
| - [After](/playground/untitled#H4sIAAAAAAAAE31WXa-jNhD9K7PsdknUQJLurtRLPqo-VKrU1327uQ8GBnBjbGSb5KZR_nvHgMlXtyIS9njO-MyZGZRzUHCBJkhez4FkNQZJ8HvTBLPAnhq3MQcUFmlvVKszZ1mbTPPGbndyZ3ndKG3hDJZne7hAoVUNYY8JV-RBPgIt2AprhA18MpZZnIQ50_twuvLHNRrDSjRXj9fwiCJTBLIKdCsxq5j9EM4gtBU3QD8GjWBZd14xWYJqLTCZg2ViDyx1W4cz4dv0hsiB49FRHkyfsCgws3GjcTKZwmYLZ2feWc9o1W8zJQ2Fb62i5JUQRNRHgs-fx3WsisKg_RN5WVn4-WrvUd9VA9tH4-AcwbfFQIpkLWByvWzqSe2sk3kyjUlOec_XPU-3TRaz_75tuvKoi19e3OvipSpamVmupJM2F_gXnnJ1lBM8oLQjHceys8R7PMFms4HwD2lRhzeEe-EsvluSrHe2TJdo4wMTLY48XKwPzm0KGm2r5ajFtRYU4TWOY7-ddWHfxhDP0QkQhnf5PWRnVVkKnIx8fZsOb5dR16nwG4TCCRdCMphWQ7z1_DoOcp3zA2SCGbPZBa5jd0G_TRxmc36Me-mG6A7l60XIlMs8ce2-OXtrDyBItdz6qVjPadObzx-RZdV1nJjx64tXad1sz962njceOHfAzmk9JzrbXqg1lw3NkZL7vgE257t-uMDcO6attSSokpmgFqVMO2U93e_dDlzOUKsc-3t6zNZp6K9cG3sS2KGSUqiUiUmq8tNYoJwbmvpTAoXA96GyjCojI26xNglk6DpwOPm7NdRYp4ia0JL94bTqRiGB5WJxqFY37RGPoz3c6i4jP3rcUA7wmhqNywQW7om_YQ2L4UQdUBdCHSPiOQJ8bFcxHzeK0jKBY0XcV95SkCWlD9t-9eOM3TLKucauiyktJdpaPqT19ddF4wFHntsqgS-_XE01e48GMwnw02AtWZP02QyGVOkcNfk072CU4PkduZSWpVYt9SkcmJ64hPwHpWF5ziVls3wIFmmW89Y83vMeGf5PBxjcyPSkXNy10J18t3x6-a6CDtBq6SGklNKeazFyLahB3PVIGo2UbhOgGi9vKjzW_j6xVFFD17difXx5ebll0vwvkcGpn4sZ9MN3vqFYsJoL6gUuK9TcPrO_PxgzWMRfflSEr2NHPJf6lj1957rRpH8CNMG84JgHidUtXt4u_wK21LXERAgAAA==)
 | |
| 
 | |
| <!-- prettier-ignore -->
 | |
| ```svelte
 | |
| <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>
 | |
| ```
 |