DEV Community

Arek Nawo
Arek Nawo

Posted on • Originally published at areknawo.com

Making a TODO App in Svelte

Rumor has it that a new JS framework is created every 6 minutes. No surprise then that only a few of them come out on top, with the most recent addition to this group being Svelte.

So, in this tutorial, I’d like to guide you through your first steps into the world of Svelte by making a simple TODO app. Hope you'll enjoy it!

Setup

Time is money and when setting up a playground for anything you’d like to only experiment with, you want to do this as fast as possible. Thus, for this tutorial, I recommend you use CodeSandbox with its Svelte template or one of the official Svelte boilerplates available on GitHub, e.g.:

npx degit sveltejs/template svelte-todo
cd svelte-todo
npm install
Enter fullscreen mode Exit fullscreen mode

Otherwise, if you’ve got some time to spend, you can configure pretty much any of the most popular JS bundlers (Rollup, Webpack, Parcel) with an additional Svelte plugin/loader. You can check out the list of pretty much all Svelte integrations in this repo.

Code

Anyway, no matter what setup method you've used, all our work will be happening within a single App.svelte file.

Let's do a few preparations first:

<script></script>

<svelte:head>
  <link
    rel="stylesheet"
    type="text/css"
    href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css"
  />
  <script src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
</svelte:head>

<main class="container is-fluid">
  <div class="columns is-centered is-vcentered is-mobile">
    <div class="column is-narrow" style="width: 70%">
      <h1 class="has-text-centered title">Svelte TODO</h1>
      <form></form>
      <ul></ul>
    </div>
  </div>
</main>
Enter fullscreen mode Exit fullscreen mode

Our code consists of 3 main sections:

  • <script> - a block containing all the required JavaScript code.
  • <svelte:head> - a special Svelte-provided block that allows us to insert elements into the <head> tag, without leaving the comfort zone of our Svelte component.
  • <main> - a block which is part of our app's template.

Now, I'm using the <svelte:head> element to import the Bulma CSS framework and Font Awesome library from external CDNs. The first will be used to make our app look more stylish, while the second gives us access to all the free Font Awesome icons out there. You can also import these resources using NPM or other methods (which are listed on the respective projects' websites), but CDN is certainly the simplest and thus the best for our use.

There's one thing to note about our use of a CSS framework. As Svelte is a somewhat young framework, and doesn't have its own "established" component library, like Vue or React do, CSS frameworks are the easiest ways to make a Svelte app look good without having to write all the code on your own. That's why we're using Bulma here, and why I recommend you also use this or other CSS frameworks in your own Svelte apps.

JavaScript

With the fundamentals of our app in place, we can start writing the actual JS code. Surprisingly enough, there's not much of it!

let todos = [];
let input = "";

function addTodo() {
  if (input) {
    todos = [
      ...todos,
      {
        text: input,
        id: Math.random()
            .toString(36)
            .substr(2, 9)
      }
    ];
  }
  input = "";
}

function removeTodo(id) {
  const index = todos.findIndex(todo => todo.id === id);
  todos.splice(index, 1);
  todos = todos;
}
Enter fullscreen mode Exit fullscreen mode

Because Svelte is a compiler, its reactivity system can be based on simple local variables. In our case, there're only 2 of those:

  • todos which stores all out TODO objects.
  • input which stores the user input.

In addition to these 2 variables, we've got 2 simple functions - addTodo() and removeTodo(), both doing exactly what their names imply.

One important detail to note here is the weird todos = todos assignment in the removeTodo() function. In vanilla JS such operation would be pointless, but here, Svelte relies on such assignments to detect whether it should update the view or not. Svelte doesn't recognize methods like splice() (which we use to remove a TODO from the array) or push() - only variable and property assignments. That's why we have to do the todos = todos operation - to trigger the view update.

On the other hand, in addTodo() function, we could use push() and do the same assignment trick as we do in removeTodo(), but we use a much nicer and cleaner spread syntax instead, where an assignment is simply required.

The form

With the JS code ready, we can proceed to make the actual template. Let's start with a form that lets the user add new TODOs:

<form
  class="field has-addons"
  style="justify-content: center"
  on:submit|preventDefault="{addTodo}"
>
  <div class="control">
    <input bind:value="{input}" class="input" type="text" placeholder="TODO" />
  </div>
  <div class="control">
    <button class="button is-primary">
      <span class="icon is-small">
        <i class="fas fa-plus"></i>
      </span>
    </button>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

We won't be focusing on all the CSS classes in here as they're all quite self-explanatory and taken straight from the Bulma CSS framework. Instead, let's zoom in on all the Svelte features used in the snippet!

On the very upper <form> element, we use the on: directive to listen to the form's submit event. The |[modifier] syntax allows us to apply modifiers to the listener, like preventDefault, in order to prevent the page from reloading on form submission. Next, we use the bind: directive with <input> element to bind the element's value property to the input variable. It's all we have to do, as Svelte will take care of the rest on its own.

Currently, our app should look like this:

Svelte TODO form

TODO list

Right now, there's not much left to do other than the TODO list itself. Thankfully, with Svelte it's quite easy!

<ul class:list={todos.length > 0}>
    {#each todos as todo (todo.id)}
        <li class="list-item" transition:slide="{{duration: 300, easing: elasticInOut}}">
            <div class="is-flex" style="align-items: center">
                <span class="is-pulled-left">{todo.text}</span>
                <div style="flex: 1"></div>
                <button class="button is-text is-pulled-right is-small" on:click={()=> removeTodo(todo.id)}>
                    <span class="icon">
                        <i class="fas fa-check"></i>
                    </span>
                </button>
            </div>
        </li>
    {:else}
        <li class="has-text-centered" transition:slide="{{delay: 600, duration: 300, easing: elasticInOut}}">
            Nothing here!
        </li>
    {/each}
</ul>
Enter fullscreen mode Exit fullscreen mode

First, we add the class: directive to our upper <ul> element. This is required to trigger a certain class name - in this case, list - when a provided condition is met. We use it as we don't want to apply the list class when there are no TODOs to be shown. This is necessary as we want to esthetically show the "Nothing here!" message.

Next, we see our first and the only used Svelte block - {#each}. It iterates over an array of values (here it's the todos array) and renders the provided template while passing the data object under the specified name (todo). The last thing here is the key expression ((todo.id)) which helps Svelte optimize the list rendering process by assigning the specified key to each item.

We also make a use of the {:else} clause, which will be rendered when the length of the passed array is equal to 0. It's the ideal place for our "Nothing here!" message.

Each item rendered with the use of {#each} block has access to the item's data through the todo object. We take advantage of that when displaying the TODO text with {todo.text} and listening to the click event with on: directive and inline handler.

Lastly, to make our app slightly more appealing, we use Svelte's built-in slide transition, to smoothly animate all the TODO items and the "Nothing here!" message.

To do this we first have to import the necessary transition and easing functions from the Svelte's library, at the top of our JS code:

import { slide } from "svelte/transition";
import { elasticInOut } from "svelte/easing";
Enter fullscreen mode Exit fullscreen mode

Then, we can use them within our template through the transition: directive. It takes a transition function (either Svelte's built-in or your own) and a config object, which varies depending on the transition. In our case, the config allows for setting values like animation delay, duration and the easing function (elasticInOut).

The results

With all set and done, here's our TODO app in its full glory:

Feel free to play with it on your own - either through the provided playground or by following the tutorial on your own.

Final thoughts

Overall, I'm really surprised by how easy and comfortable it is to work with Svelte. The advantages of having a compiler are not only visible on the client side (like the improved performance and smaller bundles), but also from the developer perspective with features like local variables-based reactivity and more. Apart from the small (but growing) community and ecosystem of libraries and tools, Svelte really is a viable option to consider for your next web app!

If you like the post consider sharing it and following me on Twitter, Facebook, or visit my personal blog. If you're interested in my content, I also recommend checking out my YouTube channel. Again, thanks for reading this piece and have a nice day!

Top comments (3)

Collapse
 
devhammed profile image
Hammed Oyedele

Nice one but let's simplify the removeTodo function:

  function removeTodo (id) {
    todos = todos.filter(todo => todo.id !== id)
  }
Collapse
 
engmms profile image
engmms

nice tutorial

Collapse
 
sm0ke profile image
Sm0ke

Super nice. Thanks for writing this.