I want to build one handbook but one is already available so lets use it for our learning
Summary
- Introduction to Svelte
- Svelte Components
- Handling State in Svelte
- Svelte Reactivity
- Svelte Props
- Cross-component State Management in Svelte
- Slots
- Svelte Lifecycle events
- Conditional Logic in Templates
- Looping in Svelte Templates
- Promises in Svelte Templates
- Working with Events in Svelte
- Where Do We Go From Here
introduction
Svelte is an exciting Web framework that offers a fresh new take on how to build Web applications.
If you are already experienced in React, Vue, Angular or other frontend frameworks you might be pleasantly surprised by Svelte.
My first impression with Svelte was that it all feels so much more like plain JavaScript than working with other frameworks. Sure, you have some rules and there are templates that are not 100% JavaScript (they look more like HTML) but most of the things that are complicated with other frameworks are very simple and lightweight with Svelte.
And my first impression has been confirmed by further usage of the framework and its ecosystem of tools.
Compared to React, Vue, Angular and other frameworks, an app built using Svelte is compiled beforehand so you don't have to serve the whole framework to every one of your site visitors. As a result, the fruition of the experience is smoother, consumes less bandwidth, and everything feels faster and more lightweight.
At deployment, Svelte disappears and all you get is plain (and fast!) JavaScript.
How to get started with Svelte
To use Svelte, you need to have Node.js installed because all the tooling we're going to use is based on Node. Check out my tutorial how to install Node.js post if you don't have it already!
And make sure it's the latest version (how to update Node.js).
If you don't want to install Node, the Svelte website provides a very cool REPL (Read-Eval-Print Loop) at https://svelte.dev/repl. It's handy to test small Svelte apps and to experiment with things.
Node installs the npx
command, which is a handy way to run Node commands. In particular, we're going to run this:
npx degit sveltejs/template firstapp
This will download and run the degit command, which in turn downloads the latest code of the Svelte project template living at https://github.com/sveltejs/template, into a newly created firstapp
folder. Make sure that git is installed on your machine and added to the PATH variable, otherwise the degit command won't work. In case things are still not working out for you, you can alternatively 'Clone or download' the template project and then delete the hidden .git
folder, which is basically the same thing that the degit
command does (only difference is that the folder is called template
instead of firstapp
).
Now go into that firstapp
folder and run npm install
to download the additional dependencies of the template. At the time of writing, these are the dependencies of that project template:
"npm-run-all"
"rollup"
"rollup-plugin-commonjs"
"rollup-plugin-livereload"
"rollup-plugin-node-resolve"
"rollup-plugin-svelte"
"rollup-plugin-terser"
"svelte"
As you can see, it's the Svelte core, plus Rollup (a Webpack alternative) and some of its plugins. Plus npm-run-all
, a CLI tool that is used to run multiple npm scripts in parallel or sequential.
We're now ready to run our Svelte site in development mode, by running
npm run dev
This will start the app on localhost, on port 5000, by default:
If you point your browser there, you'll see the "Hello world!" example:
You're now ready to open the code in your favorite editor. The src
folder contains all you need to tweak the app: the main.js
file:
This file is the entry point and in this case initializes the App component, which is defined in App.svelte
, a single file component:
<script>
export let name;
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>Hello {name}!</h1>
svelte components
Modern Web development is very much focused on components, and Svelte is no different.
What is a component? A component is an atomic part of the application that is self-contained and optionally references other components to compose its output.
In other words, it's a compartmentalized part of the application. A form can be a component. An input element can be a component. The whole application is a component.
Svelte components contain all that's needed to render a piece of the UI. Every Svelte component is declared in a .svelte
file, and in there you'll find the content (markup), the behavior (JavaScript), and the presentation (CSS) without having to define separate files.
Which is a sane way to define a piece of the UI because you don't need to search for the items that affect the same element across various files.
Here's a sample component, which we'll store in a file called Dog.svelte
:
<script>
export let name;
</script>
<style>
h1 {
color: purple;
}
</style>
<h1>The dog name is {name}!</h1>
Any JavaScript must be put in the script
tag.
The CSS you have in the style
tag is scoped to the component and does not "leak" outside. If another component has an h1
tag, this style will not affect that. This is very handy when reusing components you already wrote for other applications, for example, or when you include Open Source libraries published by other people.
For example, a few weeks ago I included a date picker component built with Svelte in an application and none of the stylings of the component leaked outside of it, and none of the CSS I wrote into the app modified the look of the date picker.
Importing the component in other components
A component can, as said, be used by other components.
Other components can now import the Dog
component in their code.
For example here's a House
component, defined in a House.svelte
file, in the same folder of Dog.svelte
:
<script>
import Dog from './Dog.svelte';
</script>
You can now use the Dog component like an HTML tag:
<script>
import Dog from './Dog.svelte';
</script>
<Dog />
Exporting specific functions from a component
As you saw above, to export the component we didn't have to do anything, because the component itself is the default export.
What if you want to export something other than the component markup and its associated and built-in functionality?
You must write all the functions you want to export from a special script
tag with the context="module"
attribute.
Here's an example. Say you have a Button component in Button.svelte
:
<button>A button</button>
and you want to provide other components the ability to change the color of the button.
A better solution for this use case is to use props, which is something we'll talk about in the next chapter. But stick with me for this example
You can provide a function, called changeColor
.
You write and export it in this special script
tag:
<script context="module">
export function changeColor() {
//...logic to change color..
}
</script>
<button>A button</button>
Note that you can have another "normal" script tag, in the component.
Now other components can import Button, which is the default export, and the changeColor
function too:
<script>
import Button, { changeColor } from './Button.svelte';
</script>
Now that is probably a silly example, but knowing you have this functionality at your disposal can be quite helpful.
handling state in sveltejs
Every component, in addition to defining the markup, the CSS and the JavaScript logic, can host its own state.
What is state? State is any data that's needed to make the component render what it's rendering.
For example, if a form input field has the string "test" written into it, there'll be a variable somewhere holding this value. That's the state of the input field.
The field is selected? A variable somewhere will register this fact. And so on.
State is hosted in the script
part of a component:
<script>
let count = 0;
</script>
Now, if you come from other frameworks in the frontend space like Vue or React, you might think "how do I update this value?" - and for a good reason, as those frameworks make this operation rather unintuitive, I'd say.
One great thing about Svelte is that you don't need to do anything special to update the state of a component.
All you need is an assignment. A simple JavaScript assignment, using the =
operator for example.
Say you have a count
variable. You can increment that using, simply, count = count + 1
, or count++
:
<script>
let count = 0;
const incrementCount = () => {
count++;
};
</script>
{count} <button on:click="{incrementCount}">+1</button>
This is nothing groundbreaking if you are unfamiliar with how modern Web frameworks handle state, but in React you'd have to either call this.setState()
, or use the useState()
hook.
Vue takes a more structured approach using classes and the data
property.
Having used both, I find Svelte to be a much more JavaScript-like syntax.
We need to be aware of one thing, which is learned pretty quickly: we must also make an assignment when changing the value.
Svelte always wants an assignment, otherwise it might not recognize that the state changed.
For simple values like strings and numbers, that's mostly a given, because all methods on String return new strings, and same for numbers - they are immutable.
But for arrays? We can't use methods that alter the array. Like push()
, pop()
, shift()
, splice()
... because there's no assignment. They change the inner data structure, but Svelte can't detect that.
Well, you can still use them, but after you've done your operation, you reassign the variable to itself, like this:
let list = [1, 2, 3];
list.push(4);
list = list;
Which is a bit counter-intuitive, but you'll quickly remember it.
Or you can use use the spread operator to perform operations:
let list = [1, 2, 3];
list = [...list, 4];
Svelte Reactivity
In Svelte you can listen for changes in the component state, and update other variables.
For example if you have a count
variable:
<script>
let count = 0;
</script>
and you update it by clicking a button:
<script>
let count = 0;
const incrementCount = () => {
count = count + 1;
};
</script>
{count} <button on:click="{incrementCount}">+1</button>
You can listen for changes on count
using the special syntax $:
which defines a new block that Svelte will re-run when any variable referenced into it changes.
Here's an example:
<script>
let count = 0;
const incrementCount = () => {
count = count + 1;
};
$: console.log(`${count}`);
</script>
{count} <button on:click="{incrementCount}">+1</button>
I used the block:
$: console.log(`${count}`);
You can write more than one of them:
<script>
$: console.log(`the count is ${count}`);
$: console.log(`double the count is ${count * 2}`);
</script>
And you can also add a block to group more than one statement:
<script>
$: {
console.log(`the count is ${count}`);
console.log(`double the count is ${count * 2}`);
}
</script>
I used a console.log() call in there, but you can update other variables too:
<script>
let count = 0;
let double = 0;
$: {
console.log(`the count is ${count}`);
double = count * 2;
console.log(`double the count is ${double}`);
}
</script>
Svelte props
You can import a Svelte component into any other component using the syntax import ComponentName from 'componentPath'
:
<script>
import SignupForm from './SignupForm.svelte';
</script>
The path is relative to the current component path.
./
means "this same folder". You'd use../
to go back one folder, and so on.
Once you do so, you can use the newly imported component in the markup, like an HTML tag:
<SignupForm />
In this way, you are forming a parent/child relationship between the two components: the one that imports, and the one that is imported.
Often you want to have the parent component pass data to the child component.
You can do so using props. Props behave similarly to attributes in plain HTML, and they are a one-way form of communication.
In this example we pass the disabled
prop, passing the JavaScript value true
to it:
<SignupForm disabled="{true}" />
In the SignupForm component, you need to export the disabled
prop, in this way:
<script>
export let disabled;
</script>
This is the way you express the fact that the prop is exposed to parent components.
When using the component, you can pass a variable instead of a value, to change it dynamically:
<script>
import SignupForm from './SignupForm.svelte';
let disabled = true;
</script>
<SignupForm disabled="{disabled}" />
When the disabled
variable value changes, the child component will be updated with the new prop value. Example:
<script>
import SignupForm from './SignupForm.svelte';
let disabled = true;
setTimeout(() => {
disabled = false;
}, 2000);
</script>
<SignupForm disabled="{disabled}" />
Sharing data using Store
We've already seen how Svelte makes handling the state of a single component very easy.
But how do we pass state around across components?
Passing state around using props
The first strategy is common to other UI frameworks and it's passing state around using props, lifting the state up.
When a component needs to share data with another, the state can be moved up in the components tree until there's a common parent to those components.
The state needs to be passed down until it reaches all the components that need this state information.
This is done using props, and it's a technique that I think is the best as it's simple.
The context API
However, there are cases where props are not practical. Perhaps 2 components are so distant in the components tree that we'd have to move state up to the top-level component.
In this case, another technique can be used and it's called context API, and it's ideal when you want to let multiple components communicate with descendants, but you don't want to pass props around.
The context API is provided by 2 functions which are provided by the svelte
package: getContext
and setContext
.
You set an object in the context, associating it to a key:
<script>
import { setContext } from 'svelte';
const someObject = {};
setContext('someKey', someObject);
</script>
In another component you can use getContext
to retrieve the object assigned to a key:
<script>
import { getContext } from 'svelte';
const someObject = getContext('someKey');
</script>
You can only use getContext
to retrieve a key either in the component that used setContext
or in one of its descendants.
If you want to let two components living in 2 different component trees communicate there's another tool for us: stores.
Using Svelte stores
Svelte stores are a great tool to handle your app state when components need to talk to each other without passing props around too much.
You must first import writable
from svelte/store
:
import { writable } from 'svelte/store';
and create a store variable using the writable()
function, passing the default value as the first argument:
const username = writable('Guest');
This can be put into a separate file which you can import into multiple components, for example, called store.js
(it's not a component, so it can be in a .js
file instead of .svelte
):
import { writable } from 'svelte/store';
export const username = writable('Guest');
Any other component now loading this file can access the store:
<script>
import { username } from './store.js';
</script>
Now the value of this variable can be set to a new value using set()
, passing the new value as the first argument:
username.set('new username');
And it can be updated using the update()
function, which differs from set()
because you don't just pass the new value to it - you run a callback function that is passed the current value as its argument:
const newUsername = 'new username!';
username.update((existing) => newUsername);
You can add more logic here:
username.update((existing) => {
console.log(`Updating username from ${existing} to ${newUsername}`);
return newUsername;
});
To get the value of the store variable once, you can use the get()
function exported by svelte/store
:
import { writable, get } from 'svelte/store';
export const username = writable('Guest');
get(username); //'Guest'
To create a reactive variable that's updated whenever the store value changes instead, you can prepend the store variable using $
(in this example $username
). Using that will make the component re-render whenever the stored value changes.
Svelte considers
$
to be a reserved value and will prevent you to use it for things that are not related to stores values (which might lead to confusion), so if you are used to prepending DOM references using$
, don't do it in Svelte.Another option, best suited if you need to execute some logic when the variable changes, is to use the
subscribe()
method ofusername
:
username.subscribe((newValue) => {
console.log(newValue);
});
In addition to writable stores, Svelte provides 2 special kinds of stores: readable stores and derived stores.
Svelte Readable Stores
Readable stores are special because they can't be updated from the outside - there's no set()
or update()
method. Instead, once you set the initial state, they can't be modified from the outside.
The official Svelte docs show an interesting example using a timer to update a date. I can think of setting up a timer to fetch a resource from the network, perform an API call, get data from the filesystem (using a local Node.js server) or anything else that can be set up autonomously.
In this case instead of using writable()
to initialize the store variable, we use readable()
:
import { readable } from 'svelte/store';
export const count = readable(0);
You can provide a function after the default value, that will be responsible for updating it. This function receives the set
function to modify the value:
<script>
import { readable } from 'svelte/store';
export const count = readable(0, (set) => {
setTimeout(() => {
set(1);
}, 1000);
});
</script>
In this case, we update the value from 0 to 1 after 1 second.
You can setup an interval in this function, too:
import { readable, get } from 'svelte/store';
export const count = readable(0, (set) => {
setInterval(() => {
set(get(count) + 1);
}, 1000);
});
You can use this in another component like this:
<script>
import { count } from './store.js';
</script>
{$count}
Svelte Derived Stores
A derived store allows you to create a new store value that depends on the value of an existing store.
You can do so using the derived()
function exported by svelte/store
which takes as its first parameter the existing store value, and as a second parameter a function which receives that store value as its first parameter:
import { writable, derived } from 'svelte/store';
export const username = writable('Guest');
export const welcomeMessage = derived(username, ($username) => {
return `Welcome ${$username}`;
});
<script>
import { username, welcomeMessage } from './store.js';
</script>
{$username} {$welcomeMessage}
Every component in Svelte fires several lifecycle events that we can hook into that help us implement the functionality we have in mind.
In particular, we have
onMount
fired after the component is renderedonDestroy
fired after the component is destroyedbeforeUpdate
fired before the DOM is updatedafterUpdate
fired after the DOM is updated
We can schedule functions to happen when these events are fired by Svelte.
We don't have access to any of those methods by default, but we can import them from the svelte
package:
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
</script>
A common scenario for onMount
is to fetch data from other sources.
Here's a sample usage of onMount
:
<script>
import { onMount } from 'svelte';
onMount(async () => {
//do something on mount
});
</script>
onDestroy
allows us to clean up data or stop any operation we might have started at the component initialization, like timers or scheduled periodic functions using setInterval
.
One particular thing to notice is that if we return a function from onMount
, that serves the same functionality of onDestroy
- it's run when the component is destroyed:
<script>
import { onMount } from 'svelte';
onMount(async () => {
//do something on mount
return () => {
//do something on destroy
};
});
</script>
Here's a practical example that sets a periodic function to run on mount, and removes it on destroy:
<script>
import { onMount } from 'svelte';
onMount(async () => {
const interval = setInterval(() => {
console.log('hey, just checking!');
}, 1000);
return () => {
clearInterval(interval);
};
});
</script>
Comments