DEV Community

Cover image for I created a Quiz app using Svelte and now I cannot go back to any other framework.
Manan Joshi
Manan Joshi

Posted on • Updated on

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

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
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
  • 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>
Enter fullscreen mode Exit fullscreen mode

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');
Enter fullscreen mode Exit fullscreen mode
  • 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;
Enter fullscreen mode Exit fullscreen mode
  • 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;
}
Enter fullscreen mode Exit fullscreen mode
  • 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';
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  • 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}
Enter fullscreen mode Exit fullscreen mode
  • 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}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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!

As always, connect with me on Twitter and Instagram.

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

Cheers.

Top comments (28)

Collapse
 
juancarlospaco profile image
Juan Carlos

Svelte or Nim for Frontend. ❀️

Collapse
 
manan30 profile image
Manan Joshi

Svelte all day long.

Collapse
 
mandaputtra profile image
Manda Putra

Nim? nim-lang?

Collapse
 
juancarlospaco profile image
Juan Carlos

Yes Nim lang nim-lang.org
Svelte is great, but Svelte cant do Backend, Nim can do Backend, and Frontend.
:)

Thread Thread
 
mandaputtra profile image
Manda Putra

Wow, yeah nim is a good language. Good to hear someone using it on production server. Nice.

Thread Thread
 
thebeachmaster profile image
Arthur Kennedy Otieno

For Backend you can use Sapper... It's build by the Svelte team and is just as simple, fast and as lightweight.

Thread Thread
 
mandaputtra profile image
Manda Putra

No no nim-lang is another new language, like a scripting language. You can check that out on their website, its like C++ replacement. Not a frontend framework

Thread Thread
 
juancarlospaco profile image
Juan Carlos

Nim can do Frontend, thats what I use.

Going to check Sapper now, thats interesting.

Thread Thread
 
mandaputtra profile image
Manda Putra

Oich I though you use nim for your API too, I'm sorry

Collapse
 
opshack profile image
Pooria A

Great post thanks for sharing.

I don't get this part tho:

"making it orders of magnitude faster than frameworks or libraries that ship with a virtual DOM implementation"

Wasn't the whole premise of VDOM to make things faster since DOM is slow?

Collapse
 
manan30 profile image
Manan Joshi • Edited

Long story short: Diffing isn't free


Explanation

The main reason we have been using things like VDOM is to compare if the data has changed or not and if so when to re-render the new changes. There are many diffing algorithms that are fast and we would update something if it has changed, but updating things directly will always be faster. In libraries like React the virtual DOM tries to compare its state with the current DOM state and if there is any difference it will ask the reconciler to apply those changes to the DOM. The part where it gets slow is the reconciliation phase. This is because diffing isn't free. It is nothing but just pure overhead. For more info, this is what the react docs have to say about virtual dom and this is what Svelte has to say about it.

Collapse
 
opshack profile image
Pooria A

Thank you for the answer, the article on the Svelte website makes total sense. I guess I need to learn more about how Svelte is updating the DOM without any diffing algo.

Collapse
 
narenandu profile image
Narendra Kumar Vadapalli

Having script, style and
Html all scoped in the same component seems to be similar to Vue! Not used Vue a lot, but is the usage expressions also similar? Eg:iterable with #each

Collapse
 
manan30 profile image
Manan Joshi

I also haven't used Vue a lot but the one major difference between Svelte and Vue is that Svelte is a compiler and not a framework.

Collapse
 
redeemefy profile image
redeemefy • Edited

I like they way Svelte approaches the web. This is more align with the lean web thinking.

Collapse
 
manan30 profile image
Manan Joshi

Agreed 100%.

Collapse
 
redeemefy profile image
redeemefy

It is so difficult to fight the industry. Nowadays we think that every problem is a nail and we want to use the Ract/Vue hammer to do the job.

Collapse
 
inozex profile image
Tiago Marques

I think Vue3 is gonna have a similar way of working in terms of component file

Collapse
 
manan30 profile image
Manan Joshi

Yes true. At the end of the day it's just semantics.

Collapse
 
pomfrit123 profile image
***

It wont compile, it asks for a utils.js file

Collapse
 
manan30 profile image
Manan Joshi

As I wanted to keep this article small all files haven't been included.

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.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.