DEV Community

Phong Duong
Phong Duong

Posted on • Edited on • Originally published at phongduong.dev

How to create a simple application with Svelte

I have started learning Svelte recently. I think creating something is a best way to learn anything. So I created a simple application that fetches dog's images from http://dog.ceo/ with Svelte. In this tutorial, I will show you how I created it.

Set up

Run the following command in your folder

npx degit sveltejs/template dog-image-app
cd dog-image-app
yarn install 
yarn dev
Enter fullscreen mode Exit fullscreen mode

Inside the src folder, you can see 2 files App.svelte and main.js. App.svelte is our app container you. main.js will import App.svelte and mount it into DOM

A svelte file include 3 parts: JS code inside script tag, CSS inside style tag and HTML tags. You don't need to wrap HTML inside anything. Every file is a component

Development

In App.svelte file, you keep the style and change the title of the page in h1 to whatever you want

You define a state named image in the script tag. You can remove name property or change its name to image and assign a object

// App.svelte
<script>
  let image = {
    src: "",
    alt: "Dog image"
  }
</script>
Enter fullscreen mode Exit fullscreen mode

In Svelte, you should define state with let instead of const. Because you update a state by reassigning a new value. I leave the src empty and the alt property is default for all image.

We need a component the that receives image state as property and display the image source. You create a new Image.svelte file, define a property named image and export it.

// Image.svelte
<script>
  export let image
</script>
Enter fullscreen mode Exit fullscreen mode

You may wonder how I name variables with a same name. I will explain this later. For HTML of Image component, you create a image element and spread the image property as its attribute

<img {...image} />
Enter fullscreen mode Exit fullscreen mode

In App component, you import the Image component, put it under the title and pass the image state to the Image component

// App.svelte
<script>
  import Image from "./Image.svelte"
  ...
</script>
<main>
  <h1>Dog image app</h1>
  <Image image={image}/>
</main>
Enter fullscreen mode Exit fullscreen mode

You can see that we repeat image twice. Svelte allows us to use shorthand attributes if the name and value are same.

<Image {image}/>
Enter fullscreen mode Exit fullscreen mode

Our application needs to allow users to select the dog breed they want or we can fetch random images. You create an Input component, define a state with default value and export a breedList property.

// Input.svelte
<script>
  let value = "random"
  export let breedList
</script>
<form>
  <select bind:value>
    <option value="random"></option>
  </select>
  <input type="submit" value="Get new image" />
</form>
Enter fullscreen mode Exit fullscreen mode

As you can see, we can bind state to the element's value attribute using bind:value directive. You don't need to create event handler for onInput event and update the state. The bind:value can use shorthand form if the variable's name is value. We will fetch the breed list using axios. We need to install axios and config utilities to do that

yarn add axios
Enter fullscreen mode Exit fullscreen mode
// config.js
export default {
  BASE_URL: "https://dog.ceo/api",
};
Enter fullscreen mode Exit fullscreen mode
// request.js
import axios from "axios";
import config from "./config";

const instance = axios.create({ baseURL: config.BASE_URL });

export default instance;
Enter fullscreen mode Exit fullscreen mode

We create an axios instance with dog.ceo api as base url. Next we will call the api and get the breed list.

// utils.js
import request from "./request";

export const getBreedList = async () => {
  const result = await request.get("/breeds/list/all");

  return result.data.message;
};
Enter fullscreen mode Exit fullscreen mode

We want to call this api when the application mounted and display to user. In the App component, you import onMount lifecycle event and Input component. We will call getBreedList function after the component mounted and update the breedList state

// App.svelte
<script>
  ...
  import { onMount } from "svelte"
  import Input from "./Input.svelte"
  import { getBreedList } from "./utils"
  ...
  let breedList = {}
  ...
  onMount(async () => {
    breedList = await getBreedList()
  })
</script>
<main>
  <h1>Dog image app</h1>
  <Input {breedList} />
  <Image {image} />
</main>
Enter fullscreen mode Exit fullscreen mode

We need to format the return data from api and display it for user to select.

// utils.js
// Chec if the value is a sub breed
export const checkSub = (string) => /-/.test(string);
// Capitalize the string
const capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1);

const formatString = (string) => {
  if (checkSub(string)) {
    const [sub, main] = string.split("-");

    return `${capitalize(sub)} ${capitalize(main)}`;
  }

  return capitalize(string);
};

export const formatList = (list) => {
  return Object.keys(list)
    .map((key) => {
      const subs = list[key];

      return subs.length === 0 ? key : subs.map((sub) => `${sub}-${key}`);
    })
    .reduce((pre, cur) => {
      if (typeof cur === "string") {
        return [...pre, { value: cur, label: formatString(cur) }];
      }

      return [
        ...pre,
        ...cur.map((breed) => ({ value: breed, label: formatString(breed) })),
      ];
    }, []);
};
Enter fullscreen mode Exit fullscreen mode

In Input component, we will format the breedList as soon as it is updated

// Input.svelte
<script>
  import { formatList } from "./utils"
  ...
  let formattedList = []

  $: {
    formattedList = formatList(breedList)
  }
</script>
<form>
  <select bind:value>
    <option value="random"></option>
    {#each formattedList as breed (breed.value)}
      <option value={breed.value}>{breed.label}</option>
    {/each}
  </select>
  <input type="submit" value="Get new image" />
</form>
Enter fullscreen mode Exit fullscreen mode

The code after the dollar sign will be executed whenever the component updated. To iterate through the list, we use each block. The value between the bracket will be used as the key.

Now we have the list and update the value when user selects. We will fetch the image of the dog breed which user selected or random image.

// utils.js
...
export const getRandomImage = async () => {
  const result = await request.get("/breeds/image/random");

  return result.data.message;
};

export const getImageByBreed = async (breedPath) => {
  const result = await request.get(`/breed/${breedPath}/images/random`)

  return result.data.message
}
Enter fullscreen mode Exit fullscreen mode
// App.svelte
<script>
  ...
  import { getRandomImage, getBreedList, checkSub, getImageByBreed } from "./utils"
  ...
  const getImage = async (e) => {
    const { detail: { value } } = e
    let breedPath = ""

    if (value === "random") {
        image.src = await getRandomImage()
    } else {
        if (checkSub(value)) {
            const [sub, main] = value.split("-")

            breedPath = `${main}/${sub}`
        } else {
            breedPath = value
        }

        image.src = await getImageByBreed(breedPath)
    }
  }
  ...
  onMount(async () => {
    image.src = await getRandomImage()
    breedList = await getBreedList()
  })
</script>
<main>
    <h1>Dog image app</h1>
    <Input {breedList} on:submit={getImage} />
    <Image {image} />
</main>
Enter fullscreen mode Exit fullscreen mode

In the code above, it will fetch the image based on the breed users selected and update the src property of image state. When the application mounted, we will get a random image. on:submit directive is the event listener for submit event. In the getImage event handler, I retrieve value property from detail property of event parameter instead of target as usual. This is because I forward submit event from Inputcomponent to App component. We will see how it works

// Input.svelte
<script>
  ...
  import { createEventDispatcher } from "svelte"
  ...
  const dispatch = createEventDispatcher()
  const submit = () => {
    dispatch("submit", {
      value
    })
  }
  ...
</script>
<form on:submit|preventDefault={submit}>
  <select bind:value>
    <option value="random"></option>
    {#each formattedList as breed (breed.value)}
      <option value={breed.value}>{breed.label}</option>
    {/each}
  </select>
  <input type="submit" value="Get new image" />
</form>
Enter fullscreen mode Exit fullscreen mode

We import createEventDispatcher from Svelte and create dispatch function. We call the dispatch function inside the submit event handler and pass the name of the event as the first argument and the value as the second argument. The preventDefault is the event modifier. We can chain modifiers together.

Summary

In this post, I introduced to you some concepts of Svelte and how to use them to create a simple application. My post doesn't cover concepts like stores, transition and slot. These concepts are useful when your application scales.

This post is originally published on my blog

If you prefer learning by watching video, you can watch this tutorial here

Sources

Svelte document: https://svelte.dev/docs

If you like my content and want to get more, please

Subscribe to my newsletter
Subscribe to my Youtube channel

Top comments (1)

Collapse
 
andrewbaisden profile image
Andrew Baisden

Cool Svelte looks fairly easy to learn if I was not so invested in React I would give it a try.