Home Svelte Overview
Post
Cancel

Svelte Overview

These notes mostly come from the interactive Svelte Tutorial that can be found here.

Introduction

First Component

1
2
3
4
5
<script>
	let name = 'Svelte';
</script>

<h1>Hello {name.toUpperCase()}!</h1>

Dynamic Attributes

You can use {} to control element attributes.

1
2
3
4
5
6
<script>
	let src = '/image.gif';
	let name = 'Rick Astley';
</script>

<img {src} alt="{name} dances." />

Styling

Add styles in components for component scoped styles.

1
2
3
4
5
6
7
8
9
<p>This is a paragraph.</p>

<style>
	p {
		color: purple;
		font-family: 'Comic Sans MS', cursive;
		font-size: 2em;
	}
</style>

Nested Components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Nested.svelte

<p>This is another paragraph.</p>

______
App.svelte

<script>
	import Nested from './Nested.svelte';
</script>

<p>This is a paragraph.</p>
<Nested />

<style>
	p {
		color: purple;
		font-family: 'Comic Sans MS', cursive;
		font-size: 2em;
	}
</style>

HTML tags

Strings are normally inserted as plain text, but you may want to render HTML directly into a component. Prevents a security risk though so you must manually sanitize before doing something like this.

1
2
3
4
5
<script>
	let string = `this string contains some <strong>HTML!!!</strong>`;
</script>

<p>{@html string}</p>

Reactivity

Assignments

When variables are changed, they get updated in the HTML. Each time count is updated, the DOM is updated.

1
2
3
4
5
6
7
8
9
10
11
12
<script>
	let count = 0;

	function increment() {
		count += 1;
	}
</script>

<button on:click={increment}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

Declarations

Reactive declarations allow you to update parts of a component’s state based on other parts of the state. They are recomputed each time those bits of state change.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
	let count = 0;
	$: doubled = count * 2;

	function handleClick() {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}</p>

Statements

We can also run arbitrary statements reactively. When one of the values in the reactive statement changes, then the statement is run. You could just do something simple like log to the console, but you can also have conditional and multiple lines of code in your statements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
	let count = 0;

	$: if (count >= 10) {
		alert('count is dangerously high!');
		count = 0;
	}

	function handleClick() {
		count += 1;
	}
</script>

<button on:click={handleClick}>
	Clicked {count}
	{count === 1 ? 'time' : 'times'}
</button>

Updating arrays and objects

Because Svelte’s reactivity is triggered by assignments, using array methods like push and splice won’t automatically cause updates. The name of the updated variable must appear on the left hand side of the assignment.

This works:

1
2
3
4
function addNumber() {
	numbers.push(numbers.length + 1);
	numbers = numbers;
}

Or the following shorthand:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
	let numbers = [1, 2, 3, 4];

	function addNumber() {
		numbers.push(numbers.length + 1);
	}

	$: sum = numbers.reduce((t, n) => t + n, 0);
</script>

<p>{numbers.join(' + ')} = {sum}</p>

<button on:click={addNumber}>
	Add a number
</button>

Props

Declaring props

To pass data from one component to another component, we use props (properties).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
App.svelte
---

<script>
	import Nested from './Nested.svelte';
</script>

<Nested answer={42} />

---
Nested.svelte
---
<script>
	export let answer;
</script>

<p>The answer is {answer}</p>

Default Props

1
2
3
4
5
<script>
	export let answer = 'a mystery';
</script>

<p>The answer is {answer}</p>

Spread props

You can use … to pass in an object as props and then destructure in the component you are passing it into.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
---
App.svelte
---

<script>
	import PackageInfo from './PackageInfo.svelte';

	const pkg = {
		name: 'svelte',
		version: 3,
		speed: 'blazing',
		website: 'https://svelte.dev'
	};
</script>

<PackageInfo {...pkg} />

---
PackageInfo.svelte
---
<script>
	export let name;
	export let version;
	export let speed;
	export let website;

	$: href = `https://www.npmjs.com/package/${name}`;
</script>

<p>
	The <code>{name}</code> package is {speed} fast. Download version {version}
	from
	<a {href}>npm</a>
	and <a href={website}>learn more here</a>
</p>

You can also pass in all props, even ones not declared with export: $$props but this isn’t recommended (not optimized).

Logic

If, else if, else blocks

1
2
3
4
5
6
7
8
9
10
11
<script>
	let x = 7;
</script>

{#if x > 10}
	<p>{x} is greater than 10</p>
{:else if 5 > x}
	<p>{x} is less than 5</p>
{:else}
	<p>{x} is between 5 and 10</p>
{/if}

Each blocks

Used to loop over data.

1
2
3
4
5
6
7
<ul>
	{#each cats as cat}
		<li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}">
			{cat.name}
		</a></li>
	{/each}
</ul>

You can also destructure in the each block:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script>
	let cats = [
		{
			id: 'J---aiyznGQ',
			name: 'Keyboard Cat'
		},
		{
			id: 'z_AbfPXTKms',
			name: 'Maru'
		},
		{
			id: 'OUtn3pvWmpg',
			name: 'Henri The Existential Cat'
		}
	];
</script>

<h1>The Famous Cats of YouTube</h1>

<ul>
	{#each cats as { id, name }, i}
		<li>
			<a
				target="_blank"
				href="https://www.youtube.com/watch?v={id}"
			>
				{i + 1}: {name}
			</a>
		</li>
	{/each}
</ul>

Keyed each blocks

You should generally specify a unique id in each blocks so that if you modify anything displayed in there, the DOM knows how to updated properly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
	import Thing from './Thing.svelte';

	let things = [
		{ id: 1, name: 'apple' },
		{ id: 2, name: 'banana' },
		{ id: 3, name: 'carrot' },
		{ id: 4, name: 'doughnut' },
		{ id: 5, name: 'egg' }
	];

	function handleClick() {
		things = things.slice(1);
	}
</script>

<button on:click={handleClick}>
	Remove first thing
</button>

{#each things as thing (thing.id)}
	<Thing name={thing.name} />
{/each}

Await blocks

You can await the value of promises directly in markup and show some content while you are waiting. Useful if you are loading content from an API and want to define your loading states.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script>
	async function getRandomNumber() {
		const res = await fetch(
			`https://svelte.dev/tutorial/random-number`
		);
		const text = await res.text();

		if (res.ok) {
			return text;
		} else {
			throw new Error(text);
		}
	}

	let promise = getRandomNumber();

	function handleClick() {
		promise = getRandomNumber();
	}
</script>

<button on:click={handleClick}> generate random number </button>

{#await promise}
	<p>...waiting</p>
{:then number}
	<p>The number is {number}</p>
{:catch error}
	<p style="color: red">{error.message}</p>
{/await}

Events

DOM Events

We can list to any standard events with the :on directive.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
	let m = { x: 0, y: 0 };

	function handleMousemove(event) {
		m.x = event.clientX;
		m.y = event.clientY;
	}
</script>

<div on:mousemove={handleMousemove}>
	The mouse position is {m.x} x {m.y}
</div>

<style>
	div {
		width: 100%;
		height: 100%;
	}
</style>

Inline handlers

You can declare event handlers inline instead of needing another function in your script tags.

1
2
3
4
5
6
7
8
9
10
11
<script>
	let m = { x: 0, y: 0 };
</script>

<div
	on:mousemove={(e) => {
		m = { x: e.clientX, y: e.clientY };
	}}
>
	The mouse position is {m.x} x {m.y}
</div>

Event Modifiers

Event handlers can have modifiers that alter their behaviour.

  • preventDefault — calls event.preventDefault() before running the handler. Useful for client-side form handling, for example.
  • once — remove the handler after the first time it runs

Examples here.

You can chain multiple modifiers together.

1
2
3
4
5
6
7
8
9
<script>
	function handleClick() {
		alert('no more alerts');
	}
</script>

<button on:click|once={handleClick}>
	Click me
</button>

Component Events

Components can dispatch events. In the nested component, you create an event dispatcher and give it a name. In the top level component, when adding the component into the html, you refer to the custom event with on:event_name.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
---
App.svelte
---
<script>
	import Inner from './Inner.svelte';

	function handleMessage(event) {
		alert(event.detail.text);
	}
</script>

<Inner on:message={handleMessage} />

---
Inner.svelte
---
<script>
	import { createEventDispatcher } from 'svelte';

	const dispatch = createEventDispatcher();

	function sayHello() {
		dispatch('message', {
			text: 'Hello!'
		});
	}
</script>

<button on:click={sayHello}>
	Click to say hello
</button>

Event forwarding

Components don’t bubble up to the top. If you want to listen to an event on a deeply nested component, the intermediate component must forward the event to the top level component.

In the example below, the top level component includes outer. Outer includes Inner. An event in Inner is passed to Outer, but not to the top level component by default. To get around this, we use the on:event_name directive (when calling Inner, from the Outer component) so that it can then talk to the top level component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
---
App.svelte
---
<script>
	import Outer from './Outer.svelte';

	function handleMessage(event) {
		alert(event.detail.text);
	}
</script>

<Outer on:message={handleMessage} />

---
Inner.svelte
---
<script>
	import { createEventDispatcher } from 'svelte';

	const dispatch = createEventDispatcher();

	function sayHello() {
		dispatch('message', {
			text: 'Hello!'
		});
	}
</script>

<button on:click={sayHello}>
	Click to say hello
</button>

---
Outer.svelte
---
<script>
	import Inner from './Inner.svelte';
</script>

<Inner on:message />

DOM event forwarding

We can also forward DOM Events (i.e. built in events such as click mouseover etc) using the same syntax. In the example below we are forwarding the on click event for the custom button to the app component, and then reacting to the event there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
App.svelte
---
<script>
	import CustomButton from './CustomButton.svelte';

	function handleClick() {
		alert('Button Clicked');
	}
</script>

<CustomButton on:click={handleClick} />

---
CustomButton.svelte
---
<button on:click>Click me</button>

Bindings

Text Inputs

Normally values set in the script update in the HTML. If we want this to happen the other way around (i.e. the value in the HTML updates the value of the variable in the script) then we use bindings.

1
2
3
4
5
6
7
<script>
	let name = 'world';
</script>

<input bind:value={name} />

<h1>Hello {name}!</h1>

Numeric inputs

Binding numbers automatically converts them from strings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<script>
	let a = 1;
	let b = 2;
</script>

<label>
	<input
		type="number"
		bind:value={a}
		min="0"
		max="10"
	/>

	<input
		type="range"
		bind:value={a}
		min="0"
		max="10"
	/>
</label>

<label>
	<input
		type="number"
		bind:value={b}
		min="0"
		max="10"
	/>

	<input
		type="range"
		bind:value={b}
		min="0"
		max="10"
	/>
</label>

<p>{a} + {b} = {a + b}</p>

Checkbox Inputs

Instead of binding to a value, we bind to ‘checked’ (which determines whether the box is checked or not).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
	let yes = false;
</script>

<label>
	<input type="checkbox" bind:checked={yes} />
	Yes! Send me regular email spam
</label>

{#if yes}
	<p>
		Thank you. We will bombard your inbox and sell
		your personal details.
	</p>
{:else}
	<p>
		You must opt in to continue. If you're not
		paying, you're the product.
	</p>
{/if}

<button disabled={!yes}> Subscribe </button>

Group inputs

If you have multiple inputs relating to the same value, you can use bind:group along with the value attribute. Radio inputs in the same group are mutually exclusive; checkbox inputs in the same group form an array of selected values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<script>
	let scoops = 1;
	let flavours = ['Mint choc chip'];

	let menu = [
		'Cookies and cream',
		'Mint choc chip',
		'Raspberry ripple'
	];

	function join(flavours) {
		if (flavours.length === 1) return flavours[0];
		return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
	}
</script>

<h2>Size</h2>

<label>
	<input type=radio bind:group={scoops} name="scoops" value={1}>
	One scoop
</label>

<label>
	<input type=radio bind:group={scoops} name="scoops" value={2}>
	Two scoops
</label>

<label>
	<input type=radio bind:group={scoops} name="scoops" value={3}>
	Three scoops
</label>

<h2>Flavours</h2>

{#each menu as flavour}
	<label>
		<input type=checkbox bind:group={flavours} name="flavours" value={flavour}>
		{flavour}
	</label>
{/each}

{#if flavours.length === 0}
	<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
	<p>Can't order more flavours than scoops!</p>
{:else}
	<p>
		You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
		of {join(flavours)}
	</p>
{/if}

Textarea inputs

Same as before - use bind:value.

1
2
3
4
5
6
7
8
9
10
11
12
<script>
	import { marked } from 'marked';
	let value = `Some words are *italic*, some are **bold**`;
</script>

{@html marked(value)}

<textarea bind:value={value}></textarea>

<style>
	textarea { width: 100%; height: 200px; }
</style>

Select bindings

We can also use bind:value with <select> elements.

<select bind:value={selected} on:change="{() =

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<script>
	let questions = [
		{
			id: 1,
			text: `Where did you go to school?`
		},
		{
			id: 2,
			text: `What is your mother's name?`
		},
	];

	let selected;

	let answer = '';

	function handleSubmit() {
		alert(
			`answered question ${selected.id} (${selected.text}) with "${answer}"`
		);
	}
</script>

<h2>Insecurity questions</h2>

<form on:submit|preventDefault={handleSubmit}>
	<select
		bind:value={selected}
		on:change={() => (answer = '')}
	>
		{#each questions as question}
			<option value={question}>
				{question.text}
			</option>
		{/each}
	</select>

	<input bind:value={answer} />

	<button disabled={!answer} type="submit">
		Submit
	</button>
</form>

<p>
	selected question {selected
		? selected.id
		: '[waiting...]'}
</p>

<style>
	input {
		display: block;
		width: 500px;
		max-width: 100%;
	}
</style>

Select multiple

A select can have a multiple attribute, in which case it will populate an array rather than selecting a single value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
	let flavours = ['Mint choc chip'];

	let menu = [
		'Cookies and cream',
		'Mint choc chip',
		'Raspberry ripple'
	];
</script>

<select multiple bind:value={flavours}>
	{#each menu as flavour}
		<option value={flavour}>
			{flavour}
		</option>
	{/each}
</select>

<h1>Selected flavours</h1>
{#each flavours as flavour}
<p>{flavour}</p>
{/each}

Lifecycle

onMount

Components have lifecycle (starting when it is created, and ending when it is destroyed). There are some functions that allow you to run code at key moments during that lifecycle.

onMount is the most common and it runs after the component is first rendered to the DOM. It is commonly used when loading data from an API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<script>
	import { onMount } from 'svelte';

	let photos = [];

	onMount(async () => {
		const res = await fetch(
			`https://jsonplaceholder.typicode.com/photos?_limit=20`
		);
		photos = await res.json();
	});
</script>

<h1>Photo album</h1>

<div class="photos">
	{#each photos as photo}
		<figure>
			<img
				src={photo.thumbnailUrl}
				alt={photo.title}
			/>
			{photo.thumbnailUrl}
			<figcaption>{photo.title}</figcaption>
		</figure>
	{:else}
		<!-- this block renders when photos.length === 0 -->
		<p>loading...</p>
	{/each}
</div>

onDestroy

To run code when your component is destroyed, use onDestroy. You can run it from a script you import into a component, or directly from a component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
---
utils.js
---
<script>
	import { onDestroy } from 'svelte';

	let counter = 0;
	const interval = setInterval(() => counter += 1, 1000);

	onDestroy(() => clearInterval(interval));
</script>

---
Timer.js
---
<script>
	import { onInterval } from './utils.js';
	...
</script>

<p>
	....
</p>

beforeUpdate and afterUpdate

The beforeUpdate function schedules work to happen immediately before the DOM is updated. afterUpdate is its counterpart, used for running code once the DOM is in sync with your data.

1
2
3
4
5
6
7
8
9
10
let div;
let autoscroll;

beforeUpdate(() => {
	autoscroll = div && div.offsetHeight + div.scrollTop > div.scrollHeight - 20;
});

afterUpdate(() => {
	if (autoscroll) div.scrollTo(0, div.scrollHeight);
});

Stores

Writable stores

Stores are objects that can be used to share state between different components (and even outside of components too, such as in normal js files).

For writable stores, we can use: set (to set the value for the first time), update (to update an existing value) and subscribe (to be notified when it changes) methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
---
App.svelte
---
<script>
	import { count } from './stores.js';
	import Incrementer from './Incrementer.svelte';
	import Decrementer from './Decrementer.svelte';
	import Resetter from './Resetter.svelte';

	let count_value;

	count.subscribe((value) => {
		count_value = value;
	});
</script>

<h1>The count is {count_value}</h1>

<Incrementer />
<Decrementer />
<Resetter />

---
Decrementer.svelte
---
<script>
	import { count } from './stores.js';

	function decrement() {
		count.update((n) => n - 1);
	}
</script>

<button on:click={decrement}> - </button>

---
Incrementer.svelte
---
<script>
	import { count } from './stores.js';

	function increment() {
		count.update((n) => n + 1);
	}
</script>

<button on:click={increment}> + </button>

---
Resetter.svelte
---
<script>
	import { count } from './stores.js';

	function reset() {
		count.set(0);
	}
</script>

<button on:click={reset}> reset </button>

---
stores.js
---
import { writable } from 'svelte/store';

export const count = writable(0);

Auto subscriptions

The example above has an issue - you subscribe but never unsubscribe. You would have to create the unsubscribe method. Or… there is a much easier option. Use $store_var syntax to automatically create the subscribe and unsubscribe methods. Note that this only works with store variables that are declared or or imported at the top level scope of a component.

1
2
3
4
5
6
7
8
9
10
11
12
<script>
	import { count } from './stores.js';
	import Incrementer from './Incrementer.svelte';
	import Decrementer from './Decrementer.svelte';
	import Resetter from './Resetter.svelte';
</script>

<h1>The count is {$count}</h1>

<Incrementer />
<Decrementer />
<Resetter />

Custom stores

You can create custom stores with domain specific logic. You only need to make sure that there is a subscribe method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
---
App.svelte
---
<script>
	import { count } from './stores.js';
</script>

<h1>The count is {$count}</h1>

<button on:click={count.increment}>+</button>
<button on:click={count.decrement}>-</button>
<button on:click={count.reset}>reset</button>

---
stores.js
---
import { writable } from 'svelte/store';

function createCount() {
	const { subscribe, set, update } = writable(0);

	return {
		subscribe,
		increment: () => update((n) => n + 1),
		decrement: () => update((n) => n - 1),
		reset: () => set(0)
	};
}

export const count = createCount();

Store bindings

If you have a writable store, you can bind to it (in the same way you can bind normally in svelte).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
App.svelte
---
<script>
	import { name, greeting } from './stores.js';
</script>

<h1>{$greeting}</h1>
<input bind:value={$name} />

<button on:click={() => ($name += '!')}>
	Add exclamation mark!
</button>

---
stores.js
---
import { writable, derived } from 'svelte/store';

export const name = writable('world');

export const greeting = derived(name, ($name) => `Hello ${$name}!`);

More stores

You may want to make a store readable only. More info here. Or you can have derived stored where the other value in a store is computed based on another store value (basically computed values for stores).

This post is licensed under CC BY 4.0 by the author.