Svelte 3 Tutorial for The Impatient (React) Developer

Learn the new kid in town with this Svelte 3 tutorial. Everything you need to know to start building with Svelte!

Svelte 3 Tutorial For The Impatient Developer (2019)

What is Svelte?

Svelte is a JavaScript UI library created by Rich Harris. Rich thinks that virtual DOM is just overhead and came up with Svelte which is now at its third incarnation.

But why you would want to learn Svelte? Aren’t React or Vue enough? Well, there are some interesting selling points:

  • Svelte is a compiler, not a dependency like React or Vue
  • Svelte seems to need less code for the same things that with React require 40% more LOC (source: Rich Harris)
  • Svelte has no virtual DOM, compiles to minimal “vanilla” JavaScript and seems more performing than other libraries

I didn’t do any benchmark to backup those assumption, take it with a grain of salt. In the following tutorial I’ll rather focus on the core concepts of Svelte 3.

Anyway, don’t jump on the bandwagon too fast. Svelte 3 is indeed interesting although it has some rough edges. In the meantime you can dip your toes into Svelte 3 with this tutorial and form your own opinion.

Enjoy.

Who this guide is for (requirements)

You’ll have no problem following the tutorial if you have a basic understanding of:

  • HTML, CSS, and JavaScript (ES6+)
  • import and export syntax (ES6 modules)
  • async/await syntax
  • concepts like components
  • the fetch API

If you’re just starting out maybe the tutorial is still too much for you. But don’t despair, check out the following resources and come back later.

If you need a refresh on ES6 modules check out the documentation for the import and the export statements in JavaScript. There is also the excellent ES6 Modules in depth.

And for learning more about the Fetch API check out Fetch API.

(Yes, it’s a lot of stuff for a beginner to know. Don’t blame me!).

Also make sure to have a newer version of Node.js installed on your system.

Svelte 3 tutorial: what you will learn and build

We will not build a “fullstack” application in this tutorial. Rather I will guide you through the core concepts of Svelte 3 by building some small pieces of UI. By the end you should be able to start building with Svelte, having seen how to create components, how to handle events, and so on.

And now enjoy learning Svelte!

Setting up the project

As with any modern JavaScript project we need to do all the song and dance: setting up the project. If you want to create a Git repo for the project go ahead and then clone the repo on your local machine.

Once cloned you’re ready to create a new Svelte project with degit. No worries, it’s not another tool to learn! Degit is “dumb”. It just makes copies of Git repos and in our case we’re going to clone the Svelte template into a new folder (or in your Git repo).

So to recap, create a new Git repo if you want and then clone it on your local machine:

git clone git@github.com:yourusername/svelte-tutorial.git

Then create a new Svelte project with degit in your new folder. If the folder is not empty degit will complain and you’ll need to pass the force flag:

npx degit sveltejs/template svelte-tutorial --force

Next up move into the new project and install the dependencies:

cd svelte-tutorial && npm i

And now you’re good to go!

Svelte 3 tutorial: dipping into Svelte

With the project in place let’s take a look around. Open up the project with your text editor. You’ll see a bunch of files:

  • App.svelte: the root component of the app
  • rollup.config.js: a configuration for Rollup, the module bundler that Svelte choose

Now open up App.svelte and take a look:

<script>
    export let name;
</script>

<style>
    h1 {
        color: purple;
    }
</style>

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

That’s a Svelte component! Really. All it needs is a script tag, a style tag and some HTML. name is a variable which is then used inside the HTML, interpolated between curly braces. Don’t worry too much about the export declaration for now. We’ll see what it does later.

Svelte 3 tutorial: fetching data with Svelte

To start off exploring Svelte we’ll begin straight away with the artillery: let’s fetch some data from an API.

For that matter Svelte is not so different from React: it uses a method named onMount. It is a so called lifecycle function. It’s easy to guess where Svelte borrowed the idea: from React lifecycle methods.

Now let’s create a new Svelte component named Fetch.svelte inside the src folder. Our component imports onMount from Svelte and makes a fetch request to an API. onMount takes a callback and from that callback we make the request. The data is saved into a variable named data, above onMount:

<script>
  import { onMount } from "svelte";

  let data = [];

  onMount(async function() {
    const response = await fetch("https://academy.valentinog.com/api/link/");
    const json = await response.json();
    data = json;
  });
</script>

Now open up App.svelte and import the newly created component (you can get rid of everything except the script tag):

<script>
  import Fetch from "./Fetch.svelte";
</script>

<Fetch />

As you can see the syntax for using custom components recalls React’s JSX. As it is the component just makes an API call but still won’t display anything. Let’s add more stuff to the mix.

Svelte 3 tutorial: creating lists with “each”

In React we’re accustomed to the map function for creating lists of elements. In Svelte there is a block named “each“. Let’s use it for creating a list of links. The API returns an array of objects and each objects has a title and an url. We’re going to add an “each” block now:

<script>
  import { onMount } from "svelte";

  let data = [];

  onMount(async function() {
    const response = await fetch("https://academy.valentinog.com/api/link/");
    const json = await response.json();
    data = json;
  });
</script>

{#each data as link}
// do stuff //
{/each}

Notice how “each” yields the variable data from where I extract each element as “link”. With the block in place now it’s time to generate a list of elements, just make sure to wrap each inside an ul element:

<script>
  import { onMount } from "svelte";

  let data = [];

  onMount(async function() {
    const response = await fetch("https://academy.valentinog.com/api/link/");
    const json = await response.json();
    data = json;
  });
</script>

<ul>
  {#each data as link}
    <li>
      <a href={link.url}>{link.title}</a>
    </li>
  {/each}
</ul>

Now head over your terminal, go into your project folder and run:

npm run dev

Visit http://localhost:5000/ and you should see a list of links:

Svelte 3 tutorial. Generating list of elements

Great job! You learned how to generate lists of elements in Svelte. But now let’s make our component more reusable.

Svelte 3 tutorial: passing props

The ability to reuse UI components is the “raison d’être” for these modern JavaScript libraries. In React for example there are props, custom attributes (and even function or other components) we can pass down to our components for making them more flexible.

Right now Fetch.svelte is not so reusable, the url is hardcoded. But worry not, Svelte components can receive props from the outside too. Let’s start by making url a variable (I’ll show you just the relevant part of the component):

<script>
  import { onMount } from "svelte";

  let url = "https://academy.valentinog.com/api/link/";
  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

Nice. And now there’s one trick to make url a prop: just prefix the variable with export:

<script>
  import { onMount } from "svelte";

  // export the variable to make a prop
  export let url = "https://academy.valentinog.com/api/link/";
  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

Now open up App.svelte and update the Fetch component by passing an url prop:

<script>
  import Fetch from "./Fetch.svelte";
</script>

<Fetch url="https://jsonplaceholder.typicode.com/todos" />

Now your component calls the new endpoint rather than the default url. Another nice thing is that variables marked as props may have a default value. In our example “https://academy.valentinog.com/api/link/” is the default prop acting as a fallback for when no prop is passed.

Now let’s see what happens when we need more than one prop.

Svelte 3 tutorial: multiple props and spreading

Svelte components may have multiple props of course. Let’s add another prop named title to our component:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";
  export let title = "A list of links";
  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

<h1>{title}</h1>
<ul>
  {#each data as link}
    <li>
      <a href={link.url}>{link.title}</a>
    </li>
  {/each}
</ul>

And again from App.svelte we pass the new prop:

<script>
  import Fetch from "./Fetch.svelte";
</script>

<Fetch
  url="https://jsonplaceholder.typicode.com/todos"
  title="A list of todos" />

Now, you’ll find the above approach impractical when props will start growing. Luckily there is a way to spread props in one pass. Declare the props as an object and spread them over the component:

<script>
  import Fetch from "./Fetch.svelte";
  const props = {
    url: "https://jsonplaceholder.typicode.com/todos",
    title: "A list of todos"
  };
</script>

<Fetch {...props} />

Nice isn’t it? But still I’m not satisfied. I want to make the Fetch component even more reusable. How?

Svelte 3 tutorial: children components and “render” props

Fetch is not a bad name for a component but if I look into it there is an HTML list. There is a way to pass that list from the outside, like a children prop in React? Turns out there is and in Svelte we refer to children components as slot.

As a first step I’ll remove all the markup from Fetch.svelte for replacing it with a slot. Get rid of the prop “title” as well:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";
  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

<slot />

Next up I can pass children elements to Fetch from the outside, it happens in App.svelte:

<script>
  import Fetch from "./Fetch.svelte";
  const props = {
    url: "https://jsonplaceholder.typicode.com/todos"
  };
</script>

<Fetch {...props}>
  <h1>A list of todos</h1>
  <ul>
    <li>now what?</li>
  </ul>
</Fetch>

But now we have a problem. I need data which lives inside Fetch.svelte and it’s important because I don’t want to create the list by hand.

In React you would have reached for an HOC, render props, or hooks. In other words I want to render a child component but the child should get data from the parent component.

In Svelte you can obtain the same result by passing values back to the parent component. First pass data as a prop to your slot:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";
  export let title = "A list of links";
  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

<!-- {data} is a shortand for data={data} -->
<slot {data} />

And from the outside you can access data by using the notation “let:data={data}”, here short-handed to “let:data”:

<script>
  import Fetch from "./Fetch.svelte";
  const props = {
    url: "https://jsonplaceholder.typicode.com/todos"
  };
</script>

<!-- let:data is like forwarding a component's data one level upward -->
<Fetch {...props} let:data>
  <h1>A list of todos</h1>
  <ul>
    {#each data as link}
      <li>{link.title}</li>
    {/each}
  </ul>
</Fetch>

Now I can use data from the Fetch component, and it will be available for my each block. It’s like forwarding the internal data of a component one level upward.

Seems a neat approach although it could be counter-intuitive at first. What do you think? In the next section we’ll look at event handling in Svelte.

Svelte 3 tutorial: handling events, and events modifiers

We’ll build a form component to illustrate how Svelte deals with events. Create a new file named Form.svelte. For now it will contain an input for searching and a button of type submit:

<script>

</script>

<form>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

(As an exercise you can extract every element into its own component).

Then include the new component inside App.svelte:

<script>
  import Form from "./Form.svelte";
</script>

<Form />

Now the app should render your form in the browser. At this point if you try to submit the form, the default behaviour takes in: the browser triggers a refresh.

To take control of the form in “vanilla” JavaScript I would register an event listener for the submit event. Then inside the handler I would prevent the default with event.preventDefault():

// vanilla JS example
var form = document.getElementsByTagName('form')[0]

form.addEventListener('submit', function(event){
    event.preventDefault();
});

Things are a bit different inside a Svelte component: event handlers are registered with “on:” followed respectively by event name / handler function:

<script>
  function handleSubmit(event) {
    // do stuff
  }
</script>

<form on:submit={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

Also, in Svelte there are event modifiers. The most important are:

  • preventDefault
  • stopPropagation
  • once

For stopping the default on our form we can use the appropriate modifier, preventDefault, after the event name:

<script>
  function handleSubmit(event) {
    // do stuff
  }
</script>

<form on:submit|preventDefault={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

You can also pass handleSubmit as a prop for making the component more flexible. Here’s an example, with a default prop:

<script>
  export let handleSubmit = function(event) {
    // default prop
  };
</script>

<form on:submit|preventDefault={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

That’s it. Now let’s take this simply app one step further: I want to filter the list of links. The form is already in place but we need to connect Fetch.svelte with Form.svelte. Let’s do it!

Svelte 3 tutorial: a quick recap

Let’s recap what we’ve done so far. We have two components, Fetch.svelte:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";

  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

<slot {data} />

and Form.svelte:

<script>
  export let handleSubmit = function(event) {
    // default prop
  };
</script>

<form on:submit|preventDefault={handleSubmit}>
  <label for="search">Search:</label>
  <input type="search" id="search" required />
  <button type="submit">Search</button>
</form>

next up there is App.svelte which is the root component. For convenience let’s render both Form and Fetch inside App:

<script>
  import Fetch from "./Fetch.svelte";
  import Form from "./Form.svelte";
</script>

<Form />
<Fetch let:data>
  <h1>A list of links</h1>
  <ul>
    {#each data as link}
      <li>
        <a href={link.url}>{link.title}</a>
      </li>
    {/each}
  </ul>
</Fetch>

Fetch.svelte takes the data from an API and forwards data upward. So when using the each block as a slot I can pass down data to its children.

Now I want the user to filter data depending on the search term she inputs inside the form. Looks like Form and Fetch need to communicate. Let’s see how we can implement that.

Svelte 3 tutorial: implementing the search feature

The way I see this feature is that we need a search term for filtering the data array. The search term can be a prop passed to Fetch.svelte from the outside. Open up Fetch.svelte and add the new prop searchTerm:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";

  // new prop
  export let searchTerm = undefined;

  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    data = json;
  });
</script>

<slot {data} />

(searchTerm is assigned undefined to prevent Svelte yelling at me “Fetch was created without expected prop searchTerm”).

Next up we need a new variable for holding the json response because we will filter that response depending on searchTerm. Add a new variable called jsonResponse and instead of saving json to data use jsonResponse for storing the API response:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";

  // new prop
  export let searchTerm;
  // new variable
  let jsonResponse = [];

  let data = [];

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    // save the response in the new variable
    jsonResponse = json;
  });
</script>

<slot {data} />

At this point the variable data will contain:

  • the original jsonResponse if no searchTerm is provided
  • a filtered array if searchTerm is not empty

For filtering array’s element we can match against the title property based on a RegExp. (The API returns an array of objects. Every object has title and url). A first implementation could be:

  const regex = new RegExp(searchTerm, "gi");

  const data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

Makes sense! Let’s see the complete component:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";

  // new prop
  export let searchTerm = undefined;
  // new variable
  let jsonResponse = [];

  const regex = new RegExp(searchTerm, "gi");

  const data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    // save the response in the new variable
    jsonResponse = json;
  });
</script>

<slot {data} />

At this point we need a couple of tweaks on App.svelte. searchTerm should be a dynamic prop from the outside. Then we intercept input’s value when a user submit the form. Open up App.svelte and pass searchTerm as a prop to Fetch:

<script>
  import Fetch from "./Fetch.svelte";
  import Form from "./Form.svelte";

  let searchTerm;
</script>

<Form />
<Fetch {searchTerm} let:data>
  <h1>A list of links</h1>
  <ul>
    {#each data as link}
      <li>
        <a href={link.url}>{link.title}</a>
      </li>
    {/each}
  </ul>
</Fetch>

Next up we create and pass handleSubmit as a prop to Form and inside App.svelte we save the search term entered by the user inside the variable searchTerm:

<script>
  import Fetch from "./Fetch.svelte";
  import Form from "./Form.svelte";

  let searchTerm;

  function handleSubmit() {
    const { value } = this.elements.search;
    searchTerm = value;
  }
</script>

<Form {handleSubmit} />
<Fetch {searchTerm} let:data>
  <h1>A list of links</h1>
  <ul>
    {#each data as link}
      <li>
        <a href={link.url}>{link.title}</a>
      </li>
    {/each}
  </ul>
</Fetch>

Almost done. Save all the files and run the development server. You will see … a blank page!

Svelte 3 tutorial reactivity

What’s going on? Hold tight and head over the next section!

Svelte 3 tutorial: reactivity

The way Svelte deals with computed values might not look intuitive at first. Our problem is in Fetch.svelte and comes from the following lines:

  const regex = new RegExp(searchTerm, "gi");

  const data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

If you think about it we have two computed values. regex depends on searchTerm and we want to re-compute the former every time the latter changes.

Then we have data: it should re-compute as well every time searchTerm and regex change. It’s like a spreadsheet: values can depend from other values.

Svelte takes inspiration from “reactive programming” and uses a weird syntax for the so called computed values. These values are called “reactive declarations” in Svelte 3. Here’s how you should adjust the above code:

  $: regex = new RegExp(searchTerm, "gi");

  $: data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

$: is not an alien language. It’s just plain JavaScript, and it’s called labeled statement.

Here’s the complete Fetch.svelte again:

<script>
  import { onMount } from "svelte";

  export let url = "https://academy.valentinog.com/api/link/";
  export let searchTerm = undefined;
  let jsonResponse = [];

  $: regex = new RegExp(searchTerm, "gi");

  $: data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    jsonResponse = json;
  });
</script>

<slot {data} />

Now the search feature will work like a charm:

Svelte 3 reactive form

(Filtering links at the API level would have been a better approach than fetching all the links every time).

Now check out the next section if you’re curious to see how the same “app” is implemented with React.

Svelte 3 tutorial: comparing with React

How does look like the same demo app built with React? Here is App.js which is the equivalent of App.svelte:

import React, { useState } from "react";
import Fetch from "./Fetch";
import Form from "./Form";

function App() {
  const [searchTerm, setSearchTerm] = useState("");

  const fetchProps = {
    url: "https://academy.valentinog.com/api/link/",
    searchTerm
  };

  function handleSubmit(event) {
    event.preventDefault();
    const { value } = event.target.elements.search;
    setSearchTerm(value);
  }

  return (
    <>
      <Form handleSubmit={handleSubmit} />
      <Fetch
        {...fetchProps}
        render={links => {
          return (
            <>
              <h1>A list of links</h1>
              <ul>
                {links.map(link => (
                  <li key={link.url}>
                    <a href={link.url}>{link.title}</a>
                  </li>
                ))}
              </ul>
            </>
          );
        }}
      />
    </>
  );
}

export default App;

Here I’m using the Fetch component with a render prop. I could use an hook but I wanted to show you how the same concept apply both to Svelte and React.

In other words:

  • for accessing the state of a parent component from a children component in React you can use render props (or a custom hook for sharing data fetching)
  • for accessing the state of a parent component from a Svelte slot you can forward the data upward from the parent

If you compare App.js with the Svelte counterpart (click here) you can see that a typical Svelte component is less verbose than a React equivalent.

Easily explained by the fact that in Svelte 3 there is no need to explicitly call setSomeState or similar functions. Svelte “reacts” just by assigning values to variables.

Next up Form.js, a React mirror of Form.svelte:

import React from "react";

function Form(props) {
  return (
    <form onSubmit={props.handleSubmit}>
      <label htmlFor="search">Search:</label>
      <input type="search" id="search" required={true} />
      <button type="submit">Search</button>
    </form>
  );
}

export default Form;

Nothing to see, just a function taking some props.

Finally there is Fetch.js, mirroring Fetch.svelte:

import { useState, useEffect } from "react";

function Fetch(props) {
  const { url, searchTerm } = props;
  const [links, setLinks] = useState([]);
  const regex = new RegExp(searchTerm, "gi");
  const data = searchTerm
    ? links.filter(link => link.title.match(regex))
    : links;

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(json => setLinks(json));
  }, [url]);

  return props.render(data);
}

Fetch.defaultProps = {
  url: "https://academy.valentinog.com/api/link/"
};

export default Fetch;

The above component uses hooks and render props: again, that’s unnecessary because you can extract a custom hook. And here’s Fetch.svelte:

<script>
  import { onMount } from "svelte";

  export let url = "fillThis";
  export let searchTerm = undefined;
  let jsonResponse = [];

  $: regex = new RegExp(searchTerm, "gi");

  $: data = searchTerm
    ? jsonResponse.filter(element => element.title.match(regex))
    : jsonResponse;

  onMount(async function() {
    const response = await fetch(url);
    const json = await response.json();
    jsonResponse = json;
  });
</script>

<slot {data} />

They look pretty similar to me. However those examples are nowhere near a real big app and you’re mileage may vary.

Svelte 3 tutorial: how does Svelte compares to React and Vue?

I’ve been asked what do I think of Svelte compared to React and Vue. I cannot speak for Vue because I don’t have much experience with it but I can see how Svelte borrows a lot.

Speaking of React, Svelte reasons well for me and looks more intuitive than its counterpart. At a cursory glance Svelte 3 seems like just another way of doing things, maybe smarter than React. But the ground is solid.

What’s really appealing in Svelte is that there is no virtual DOM unlike React and Vue. In other words there is no abstraction between the library and the actual Document Object Model: Svelte 3 compiles to the minimal “vanilla” JavaScript possible. That’s useful if you’re running your app in constrained environments.

To recap, Svelte is quite an interesting library but I’ll give it more time at least until the documentation, the ecosystem, and the tooling will grow mature.

Svelte 3 tutorial: resources

For learning more about Svelte I can’t recommend enough the official documentation and the examples.

The source code for this tutorial is available here.

Also make sure to watch this talk from the creator of Svelte:

Wrapping up

Where to go from here? There is a lot more to learn about Svelte 3 if you want. And there are a lot of goodies out of the box:

  • scoped styles
  • two way binding
  • state management
  • built-in animations

And before saying goodbye let me add some closing words below.

JavaScript is brutal. Libraries come and go and there’s always something new to learn. Over the years I learned to not become too tied to any particular JavaScript library but in all honesty I really like React and Redux.

React brought “components” to the masses, on the other hand the library itself requires high specialization to be mastered. Vue in contrast is more beginner friendly yet unfortunately it is not perceived as “trendy” as React (whatever that means).

Svelte 3 takes the best of both worlds: Svelte components look like Vue’s and some of React’s concepts apply as well.

Svelte is more intuitive than React, especially if we think of a beginner approaching React in the hook era. Granted, React is not going away anytime soon but I’m looking forward to see where Svelte is headed.

And for you my friend there is always my old advice: always keep learning the web platform and you will be able to master any JavaScript library coming next.

Thanks for reading and stay tuned on this blog!

4 Replies to “Svelte 3 Tutorial for The Impatient (React) Developer”

  1. This has been extremely helpful with good explanations of the issue-to-be-solved and how to do it. The fetch function was what I was really after but I learned a lot besides that, especially some extra thoughts on how to communicate between components. Thanks a lot!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.