How to build an Unsplash Search App with Svelte 3
Use Svelte 3 and the Intersection Observer API to build an Unsplash Search App. A great exercise for developing your Svelte skills.
Why is Svelte gaining popularity, and do we really need another JavaScript framework? In this article, we’ll take a look the value proposition of Svelte compared to other mainstream frameworks, and get some hands on experience by building a todo list application
Svelte is not a new JavaScript framework with its first appearance on the scene coming around late 2016. But it only just caught my attention with the release of Svelte 3 which was discussed at length on Hacker News.
Initially, I was dismissive of the whole announcement framing it in my mind as “Yet Another JavaScript Framework”. On closer inspection however, I discovered that Svelte could actually be worth paying attention to as it brings some things to the table that are not present with popular frameworks like React or Vue.
The first thing you will notice is that Svelte is easy to pick up and start using right away, perhaps because there is no weird syntax to learn. It’s really just HTML, CSS and JavaScript with some helpers.
Another big difference is that, Svelte does not use a virtual DOM. Although the use of virtual DOM has often been touted as a reason why React is so efficient at, well, reacting, Svelte proves that this step is not necessary for high performance reactivity.
I’m not going to go too deep into how Svelte works under the hood in this article. Instead, I’m going to demonstrate how to build a simple todo list app with Svelte.
Here is a live demo of what we’ll be building.
This tutorial is mostly targeted at developers who have prior experience with frameworks such as React or Vue. However, you should be able to follow through with it even if all you know is vanilla JavaScript or jQuery.
That being said, a basic understanding of the command line and git is a must. Additionally, you need a recent installation of Node.js and npm (or yarn) on your machine.
Clone this GitHub repository to your computer and cd
into the created directory. Then run npm install
from the project root to install all the dependencies specified in the package.json
file.
Speaking of the package.json
file, notice how Svelte is listed under devDependencies
. This is because Svelte is involved only at compile time and not at runtime, converting your components into imperative code with excellent performance characteristics when building for production.
After installing the dependencies, you can start the development server by running npm run dev
which should compile the app and make it accessible at http://localhost:5000.
If you look into the src
folder, you will see two files: main.js
is the entry point for the app, while App.svelte
is the root component for the app that serves to bring all other components together. In main.js
, the App
component is imported and instantiated on the <body>
element in public/index.html
.
Svelte components are composed in .svelte
files. This is where all the markup, logic and styling for a particular component will be written in. Here’s how a typical Svelte component looks like:
<script>
// component logic
</script>
<style>
/* component specific styles */
</style>
<!-- component markup -->
That’s it! You do not need to learn any special syntax to write a Svelte component, just regular ol’ JavaScript, CSS and HTML, with a few Svelte-specific additions to the HTML syntax which should be easy enough to pick up.
As an aside, you might need to install an extension for your code editor to provide syntax highlighting for .svelte
files. Vim users can try vim-svelte, while VS Code users can use Svelte for VS Code.
Open up App.svelte
and replace the <!-- component markup -->
comment with the following code:
<main>
<div class="container">
<h1 class="app-title">todos</h1>
<ul class="todo-list"></ul>
<div class="empty-state">
<svg class="checklist-icon"><use href="#checklist-icon"></use></svg>
<h2 class="empty-state__title">Add your first todo</h2>
<p class="empty-state__description">What do you want to get done today?</p>
</div>
<form>
<input class="js-todo-input" type="text" aria-label="Enter a new todo item" placeholder="E.g. Build a web app" />
</form>
</div>
</main>
Nothing new to see here. Just basic HTML that describes the structure of our application. For simplicity, the styles for the app are placed in public/global.css
while the svg icons used are defined in public/index.html
.
Next, let’s figure out how to add a todo item and render it in the application.
Add the following code between the script
tags in App.svelte
as shown below:
<script>
let todoItems = [];
let newTodo = '';
function addTodo() {
newTodo = newTodo.trim();
if (!newTodo) return;
const todo = {
text: newTodo,
checked: false,
id: Date.now(),
};
todoItems = [...todoItems, todo];
newTodo = '';
}
</script>
The todoItems
array serves to hold our todo list items while newTodo
represents the input of the user when adding a new todo item. When the addTodo
function is invoked, a new todo
object is constructed and appended to the todoItems
array. The reason why I’ve opted to use the spread operator to construct a new array instead of using todoItems.push(todo)
for example is because Svelte only updates the DOM on assignments.
There’s nothing strange about the above code, but how can we bind our markup to the logic? Let’s find out. Update the form
element in the HTML as shown below:
<form on:submit|preventDefault={addTodo}>
<input class="js-todo-input" type="text" aria-label="Enter a new todo item" placeholder="E.g. Build a web app" bind:value={newTodo}>
</form>
Svelte provides a handy way to bind functions to events using the on:event
syntax while passing in a reference to a function defined in your JavaScript logic. DOM event handlers can have modifiers that can alter their behavior. For example, when handling a form submission on the client, you typically want to do event.preventDefault()
to stop the browser from refreshing the page. Svelte provides a preventDefault
modifier that does this for you before running your event handler. That’s what the |preventDefault
part after on:submit
is all about.
If you look at the <input>
element, you will see a new bind:value
attribute and the newTodo
variable passed to it. This how two-way binding is achieved in Svelte. If you’re not familiar with this concept, it helps us update the newTodo
variable with the value of the form input and also update the form input if the newTodo
variable is updated programatically in the app logic. This is why setting newTodo
to an empty string at the end of the addTodo
function clears the form input.
Try it out. Enter a new todo item, and hit Enter. Behind the scenes, the todo item will be appended the todoItems
array, and the form input is cleared. If you want to see this in action, log todoItems
to the console at the end of addTodo()
.
Once we append a new todo to todoItems
, we want the page to be updated and the item rendered on the screen. We can do this easily in Svelte using a special each
constuct in the HTML:
<ul class="todo-list">
{#each todoItems as todo (todo.id)}
<li class="todo-item">
<input id={todo.id} type="checkbox" />
<label for={todo.id} class="tick"></label>
<span>{todo.text}</span>
<button class="delete-todo">
<svg><use href="#delete-icon"></use></svg>
</button>
</li>
{/each}
</ul>
What this means is that, for each todo in todoItems
, the li
element is appended to the DOM.If you’re coming from React or Vue, you know that you need to add a key
to each item when rendering a list. In Svelte, you can do this using the (key)
syntax in the each
constuct. This helps Svelte figure out what items are changed, added or removed in an efficient manner. In this instance, I’m using each todo’s id
as the key.
Try it out. Each new todo item added to the list will now be rendered on the page.
Next, we need a way to indicate that a todo item has been completed. That’s what the checked
property on the todo
object is for. When a todo item is done, this property needs to be toggled to true
and vice versa. Add a new toggleDone
function below addTodo
:
function toggleDone(id) {
const index = todoItems.findIndex(item => item.id === Number(id));
todoItems[index].checked = !todoItems[index].checked;
}
Then update the li
element in the each
block as follows:
<ul class="todo-list">
{#each todoItems as todo (todo.id)}
<li class="todo-item {todo.checked ? 'done' : ''}">
<input id={todo.id} type="checkbox" />
<label for={todo.id} class="tick" on:click={() => toggleDone(todo.id)}></label>
<span>{todo.text}</span>
<button class="delete-todo">
<svg><use href="#delete-icon"></use></svg>
</button>
</li>
{/each}
</ul>
Note that, as shown above, you need to place your handler in an arrow function when you want to pass arguments to the function. If you do on:click={toggleDone}
instead, the DOM event is what will be passed into the function.
Once the checked
property on a todo is set to true
, the done
class is toggled on the li
element. This has the effect of showing a checkmark in the checkbox of the item and striking out the text.
Removing an item from the list is easy enough. Add a new deleteTodo
function below toggleDone
:
function deleteTodo(id) {
todoItems = todoItems.filter(item => item.id !== Number(id));
}
Then bind it to the button
inside the li
element:
<button class="delete-todo" on:click={() => deleteTodo(todo.id)}>
<svg><use href="#delete-icon"></use></svg>
</button>
That’s it! Clicking the x
button will now remove the item from todoItems
while Svelte takes care of the DOM update.
At the moment, once a new todo item is submitted the input goes out of focus and has to be refocused manually each time you want to add another todo. This can become quite tedious if you want to add a large amount of items. To keep focus on the input, we can hook into the afterUpdate
lifecycle hook which runs after the DOM is updated.
Import afterUpdate
at the top of the <script>
tag in App.svelte
and use it as follows:
<script>
import { afterUpdate } from 'svelte';
afterUpdate(() => {
document.querySelector('.js-todo-input').focus();
});
// rest of the code
</script>
That solves the problem quite nicely.
That concludes my tutorial. I hope it has helped you learn the basics of Svelte and how you can use it to build user interfaces. Feel free to reach out in the comments if you got stuck, or if something was not clear enough.
The complete code for this tutorial can be found in this GitHub repository. If you’re interested in learning more about Svelte, consider subscribing to my email newsletter so you do not miss future tutorials.
Comments
Ground rules
Please keep your comments relevant to the topic, and respectful. I reserve the right to delete any comments that violate this rule. Feel free to request clarification, ask questions or submit feedback.