I created a Quiz app using Svelte and now I cannot go back to any other framework
Dec 1, 2019
9 min read
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.
Now, if you open up package.json
you'll see something amazing.
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 theApp.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 asvelte
file which is just syntactic sugar for writingHTML
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
andmarkup
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
orconst
to declare variables. All the variables that we declare are essentialstate
variables. And all the rules ofJavaScript
apply to these variables, soconst
variables cannot be reassigned whilelet
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 aboutprops
. In Svelte, you can declare a prop by just adding theexport
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 ourevent 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 theexpression
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 theexpression
is an iterable value whileexp
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.