I created a Quiz app using Svelte and now I cannot go back to any other framework

Dec 1, 2019

9 min read

Last Updated On Apr 13, 2021

Update: I have updated this project with animations and corrected the logic behind the app. You can find the most up to date version here and the live demo.

I was hearing about Svelte a lot and after listening to this talk I wanted to give it a try too. So I did and it turns out that Svelte is super amazing. I am used to programming with React a lot and some clear differences made me fall in love with Svelte.

What is Svelte

Svelte is a compiler that converts our declarative component-based code into JavaScript that can manipulate the DOM directly. You might have heard that Svelte is blazing fast, faster than any other framework out there and that is true. The reason behind this is because of the very fact that Svelte is more of a compiler than a framework or a library. Svelte does not use a Shadow DOM or a Virtual DOM to perform DOM updates, naturally making it orders of magnitude faster than frameworks or libraries that ship with a virtual DOM implementation. It tries to eliminate a lot of boilerplate code and is truly reactive. If you are coming from a React ecosystem like me, Svelte challenges the way you think in a lot of ways.


In this article, we will create a small trivia app and see how Svelte compares to React.

Let's start by creating a Svelte project first. Svelte just like create-react-app provides a way to bootstrap a Svelte app. Just run the code below to get up and running.

npx degit sveltejs/template my-svelte-project
cd my-svelte-project

npm install
npm run dev

Our project directory should look something like this.

directory-structure

Now, if you open up package.json you'll see something amazing.

package.json

It does not have any dependencies listed. All the dependencies are devDependencies. This is because Svelte is a compiler and all the dependencies are computed beforehand while generating the build and hence our final code doesn't ship with any of those dependencies making our build size much much smaller.

  • The main.js file is our main entry point into the application. It is like the App.js file in a React project.
  • We also see the App.svelte file. Let's open the file and understand the different parts of it.
  • If you are familiar with React we know that we usually end our React specific files with a .jsx extension. Similarly in Svelte, all our Svelte specific files end with a .svelte extension.
  • Each Svelte file contains either just markup (HTML tags) or markup with styles enclosed in the <style></style> tag or JavaScript enclosed in <script></script> tag or all three of them.
  • The best part about the Svelte component is that the styles inside of them are scoped to that component only and hence you won't run into an issue where the styles would leak into some other component.
  • If you are used to writing HTML in JS with JSX, Svelte is the exact opposite of that and you write everything in a svelte file which is just syntactic sugar for writing HTML files.

Note: If you are coming from a React background you might not be used to thinking this way but believe me this is going to help you expand your boundaries.

With that being said let's start.


First, we are going to look at the App.svelte file. This is our main file/component which serves as the entry point for the app. You can use the code below for your reference.

<script>
  // import QuizArea from './QuizArea.svelte';
</script>

<style>
  main {
    text-align: center;
    padding: 1em;
    max-width: 240px;
    margin: 0 auto;
  }

  h1 {
    text-transform: uppercase;
    font-size: 4em;
    font-weight: 100;
  }

  @media (min-width: 640px) {
    main {
      max-width: none;
    }
  }
</style>

<main>
  <!-- <QuizArea></QuizArea> -->
</main>
  • As you can see in the code, we have the script, style and markup for the component. At this point, the code does nothing fancy other than just applying some styles to the app.
  • But soon we'll uncomment the QuizArea component.

I hope that you have gotten a basic idea of the different parts in a Svelte file/component.

I have not added all the files from the project in this post but if you would like to reference the code at any moment it is available here.

Now, let's create a new QuizArea component. For that create a file called QuizArea.svelte in the src directory.

We'll look at each of the three parts separately.

  • First up we have the <styles></styles> tag. You can add any styles that you want for that component in between the <style> tag.
  • Instead of writing CSS in a separate file, in Svelte we write the styles within the component itself.
  • I have defined styles for the QuizArea component in the code below, but you can style it the way you want it.
<style>
  #main {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translateX(-50%) translateY(-50%);
    height: calc(100vh - 40%);
    width: calc(100vw - 40%);
    padding: 15px;

    background-color: white;
    border-radius: 6px;
    box-shadow: 0 0 5px white;

    text-align: left;
  }

  span {
    display: block;
    margin-top: 20px;
  }

  button {
    margin-top: 15px;
    margin-right: 15px;
    padding: 10px;
    float: right;

    color: white;
    background-color: #ff3e00;
    border: none;
    border-radius: 10px;
    cursor: pointer;
  }

  button:hover {
    box-shadow: 0 0 5px #ff3e00;
  }

  #heading {
    font-size: 24px;
    font-weight: bolder;
  }

  #difficulty {
    position: absolute;
    right: 16px;
    top: 16px;
    height: 25px;
    width: 80px;
    padding: 5px;

    background: rgb(97, 225, 230);
    color: white;
    text-align: center;
    border-radius: 16px;
  }

  #category {
    font-size: 12px;
    font-weight: normal;
  }

  #button-bar {
    position: absolute;
    bottom: 16px;
    right: 0;
  }

  #choice {
    margin-top: 16px;
    padding: 8px;

    border: 1px solid #4e5656;
    border-radius: 8px;
  }

  #choice:hover {
    cursor: pointer;
    background: green;
    border: 1px solid green;
    color: white;
  }

  #snackbar {
    position: absolute;
    left: 16px;
    bottom: 16px;
  }
</style>

This was easy, nothing fancy or Svelte specific. The only thing is we write styles in the same file as the other component code.

  • Next up we are going to talk about the <script></script> tag. We'll be writing all our JavaScript inside of this tag and here is where we'll be looking at how Svelte does things.
  • So, in Svelte we'll be using let or const to declare variables. All the variables that we declare are essential state variables. And all the rules of JavaScript apply to these variables, so const variables cannot be reassigned while let variables can be reassigned.
  • They are the same as the variables that we declare using useState() in React.
  • The best part about Svelte is that the component automatically re-renders whenever the value of state variable changes. But there is no need to call any set function.
// In Svelte
let name = 'Manan';

// Same thing in React
const [name, setName] = React.useState('Manan');

// causes component to re-render
name = 'Maitry';

// Same thing in React
setName('Maitry');
  • We have talked about state so it's natural that we talk about props. In Svelte, you can declare a prop by just adding the export keyword behind the variable declaration.
// props in Svelte
export let name;
  • The name prop can now be used in other components. We can declare any number of props as we do it in React.
  • We can even declare functions that can act as our event handlers or can serve any other purpose like fetching data, providing utility operations, etc.
// on click handler
function handleClick(change) {
  snackbarVisibility = false;

  if (change === 'f') questionNo += 1;
  else questionNo -= 1;

  question = htmlDecode(data[questionNo].question);
  answerChoices = shuffle(
    [
      ...data[questionNo].incorrect_answers,
      data[questionNo].correct_answer
    ].map(a => htmlDecode(a))
  );
  answer = htmlDecode(data[questionNo].correct_answer);
  category = htmlDecode(data[questionNo].category);
  difficulty = data[questionNo].difficulty;
}
  • We can import other modules or packages or components by using the import keyword. This is similar to what we do in React.
// imports the Snackbar component
import Snackbar from './Snackbar.svelte';

The main takeaway from this part is that we can write whatever JavaScipt we want with a few twists and the compiler will do the rest for us.

Now the question is how can we use our JavaScript variables in the HTML markup. So for the final part of the app, we will look into that.

  • It is pretty simple to render any variable. We just wrap the variable in curly braces like this {variableName}.
<!-- see how simple it is :smiley:-->
<p>Hello {name}!</p>

<!-- outputs -->
Hello Manan
  • Remember the markup inside a Svelte file is Html-ish and hence we can use built-in Svelte expressions to perform things like rendering something conditionally or looping over given values.
  • To render something conditionally we use {#if expression}<div></div> {/if}. Here the expression can be any valid variable or expression that is in scope (i.e declared inside the <script> tag)
{#if name}
<div id="snackbar">
  <Snackbar message="{correct}"></Snackbar>
</div>
{/if}
  • To loop over an array we use the {#each expression as exp}<div></div>{/each}. Here the expression is an iterable value while exp is each entry of that iterable value.
{#each answerChoices as choice}
<div id="choice" on:click="{(e) => handleAnswerChoice(e)}">
  <i>{choice}</i>
</div>
{/each}

This is just the tip of the iceberg and you can learn more about everything that Svelte can do over here.

With this, we can now stitch our component together. Copy and paste the code given below in your QuizArea.svelte file

<script>
  import { onMount } from 'svelte';
  import { htmlDecode, shuffle } from './utils.js';
  import Snackbar from './Snackbar.svelte';

  let data;

  let questionNo = 0;
  let question = 'loading...';
  let answerChoices;
  let answer;
  let category = 'loading...';
  let difficulty = 'loading...';

  let correct = false;
  let snackbarVisibility = false;
  $: score = 0;

  // function for fetching data
  function fetchData() {
    fetch('https://opentdb.com/api.php?amount=10')
      .then(resp => resp.json())
      .then(res => {
        data = res.results;
        question = htmlDecode(data[questionNo].question);
        answerChoices = shuffle(
          [
            ...data[questionNo].incorrect_answers,
            data[questionNo].correct_answer
          ].map(a => htmlDecode(a))
        );
        answer = htmlDecode(data[questionNo].correct_answer);
        category = htmlDecode(data[questionNo].category);
        difficulty = data[questionNo].difficulty;
      })
      .catch(e => console.error(e));
  }

  onMount(fetchData);

  // function for moving onto next/prev question
  function handleClick(change) {
    snackbarVisibility = false;

    if (change === 'f') questionNo += 1;
    else questionNo -= 1;

    question = htmlDecode(data[questionNo].question);
    answerChoices = shuffle(
      [
        ...data[questionNo].incorrect_answers,
        data[questionNo].correct_answer
      ].map(a => htmlDecode(a))
    );
    answer = htmlDecode(data[questionNo].correct_answer);
    category = htmlDecode(data[questionNo].category);
    difficulty = data[questionNo].difficulty;
  }

  // function to check the correctness of an answer
  function handleAnswerChoice(e) {
    if (e.target.innerText === answer && !correct) {
      correct = true;
      score += 1;
    } else if (correct) correct = false;
    snackbarVisibility = true;
  }
</script>

<style>
  #main {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translateX(-50%) translateY(-50%);
    height: calc(100vh - 40%);
    width: calc(100vw - 40%);
    padding: 15px;

    background-color: white;
    border-radius: 6px;
    box-shadow: 0 0 5px white;

    text-align: left;
  }

  span {
    display: block;
    margin-top: 20px;
  }

  button {
    margin-top: 15px;
    margin-right: 15px;
    padding: 10px;
    float: right;

    color: white;
    background-color: #ff3e00;
    border: none;
    border-radius: 10px;
    cursor: pointer;
  }

  button:hover {
    box-shadow: 0 0 5px #ff3e00;
  }

  #heading {
    font-size: 24px;
    font-weight: bolder;
  }

  #difficulty {
    position: absolute;
    right: 16px;
    top: 16px;
    height: 25px;
    width: 80px;
    padding: 5px;

    background: rgb(97, 225, 230);
    color: white;
    text-align: center;
    border-radius: 16px;
  }

  #category {
    font-size: 12px;
    font-weight: normal;
  }

  #button-bar {
    position: absolute;
    bottom: 16px;
    right: 0;
  }

  #choice {
    margin-top: 16px;
    padding: 8px;

    border: 1px solid #4e5656;
    border-radius: 8px;
  }

  #choice:hover {
    cursor: pointer;
    background: green;
    border: 1px solid green;
    color: white;
  }

  #snackbar {
    position: absolute;
    left: 16px;
    bottom: 16px;
  }

  @media screen and (max-width: 960px) {
    #main {
      width: calc(100vw - 15%);
    }
    #difficulty {
      top: -16px;
    }
  }
</style>

<div id="main">
  <span id="heading"
    >Question {questionNo + 1}
    <i id="category">(Category - {category})</i></span
  >
  <span>{question}</span>
  <div id="difficulty">{difficulty}</div>

  {#if answerChoices} {#each answerChoices as choice}
  <div id="choice" on:click="{(e) => handleAnswerChoice(e)}">
    <i>{choice}</i>
  </div>
  {/each} {/if}

  <div id="button-bar">
    {#if !(questionNo > 10)}
    <button value="Next" on:click="{() => handleClick('f')}">Next</button>
    {/if} {#if questionNo > 0}
    <button value="Back" on:click="{() => handleClick('b')}">
      Previous
    </button>
    {/if}
  </div>

  {#if snackbarVisibility}
  <div id="snackbar">
    <Snackbar message="{correct}"></Snackbar>
  </div>
  {/if}
</div>

And there we have it an app written completely in Svelte. Go ahead and use npm run dev to see your app in action. This is a really small app that demonstrates what we can do with Svelte and for me, this might revolutionize the way we design for the web and I am very much excited about what's ahead in store for us.

The main goal of this article was to give you an overview of Svelte and how awesome it is. I hope you feel a little bit more comfortable about using Svelte now.


Thoughts 💭

Please let me know your thoughts about Svelte in the discussion section below. Also, don't hesitate to ask any questions if you are stuck somewhere in the app or would like to learn more about it or are confused about any part.


Thank you for reading!

Let's connect on Twitter and Instagram.

Until next time, peace out and happy coding !!!

Cheers.