loading...

Budget Poll App from Scratch in Svelte 3

corvusetiam profile image CorvusEtiam Updated on ・5 min read

CRUD in Svelte3 (3 Part Series)

1) How to build Budget Poll App in Svelte3 2) Building Svelte 3 Budget Poll App [2] 3) Budget Poll App from Scratch in Svelte 3

Welcome in our third installment. I finally uploaded whole project to GitHub. You can clone it and test out on your machine.

What changes were made to finish the project?

We are lacking one component: Balance page, with final data about our little money poll.
Secondly I find out, that a bit of clean up would make whole experience a lot better.
And we had few bugs on our way. I will try to explain to you those changes and why I made them. Still, this is beginner level project and we don't have many occasions to use complex patterns in code.

I wrote a bit of CSS to make our project a little bit more readable. It is still missing bunch of details, but I will have to live with it. I will fix them, but currently the whole thing is working well.

Balance page

You should start with new file named Balance.svelte.
It will be super simple.

<div>
    <h2>Poll Table Balance</h2>
    <table>
        <thead>
            <tr>
                <th>No.</th>
                <th>Person</th>
                <th>Paid</th>
                <th>Balance</th>
            </tr>
        </thead>
        <tbody>
            {#each $pollState.people as person, index }
            <tr>
                <td>{index + 1}.</td>
                <td>{person.name}</td>
                <td>{ format_currency(currency, person.amount) }</td>
                <td>{ format_currency(currency, person.amount - average_amount ) }</td>
            </tr>
            {/each}
        </tbody>
    </table>
</div>
<div>
    <h2>Summary</h2>
    <div>
        <h3>Person Polling money</h3>
        <p>{ $pollState.people.length }</p>    
    </div>
    <div>
        <h3>Average amount per person</h3>
        <p>{ format_currency($pollState.poll.currency, average_amount) }</p>    
    </div>
    <div>
        <h3>Money owed to other person</h3>
        <p>{ format_currency($pollState.poll.currency, compute_owed_money()) }</p>
    </div>
</div>

All the Svelte-JS is explained earlier. There are only two new parts.

Variables average_amount and function compute_owed_money.
We are using function call over here in template -- compute_owed_money() and can get away with it, thanks to few minor details. Normally, I would push it into reactive variable to make it work ALWAYS.

This time I went with simplistic approach. Why? What is so simplistic or non-optimal here, you may ask. Ok, first things first.

When rendering the template code, functions inside evaluate just once. What I mean is that, if we didn't force reload in template on state changes template would stay intact.

<script>

    let arr = [1, 2, 3, 4];

    function test() {
        return arr[Math.floor(( arr.length ) * Math.random())];
    }

    function update() {     
        arr.push(arr.length); 
        arr = arr;      
    }
</script>
<div>
    <p>Random value is: {test()}</p>
        <button type="button" on:click={ (ev) => {  update(); } }>Click me</button>
    <ul>
    {#each arr as item}
        <li>{item}</li>
    {/each}
    </ul>
</div>

Ok, this is pretty small example. The test function will pick random value from our array arr.
Function update push new value equal to size of array into arr. And assign it again to itself, to force reactive behaviour in Svelte.

It will update our list rendering of <li>{item}</li>'s.
But what will be value inside {test()}? Will it be equal, even if our array change size?

Test it. You may use REPL provided by svelte.dev.
Why then our list changes and our test(), do not and it renders only once?

Now let me change one thing here.

<script>
    let visible = true;     
    let arr = [1, 2, 3, 4];

    function test() {
        return arr[Math.floor(( arr.length ) * Math.random())];
    }

    function update() {     
        arr.push(arr.length); 
        arr = arr;      
    }
</script>
<div>
    {#if visible}
    <p>Random value is: {test()}</p>
        <button type="button" on:click={ (ev) => {  update(); } }>Click me</button>
    <ul>
    {#each arr as item}
        <li>{item}</li>
    {/each}
    </ul>
    {/if}
    <button type="button" on:click={ev => { visible = !visible; }}>Hide and Show</button>
</div>

Try it, try to click hide and show button, then Click me button few times.
Do you have idea, why this works like that? It is because, when we are hiding parts of templates with {#if ...} or rendering with {#each} or using {#await}, we are forcing updates in template, when variable changes.

Or for that matter whole content of template.

How to achieve something like this then? How to update our test() value or owed money.
And why, our compute_owed_money() works?

First things first, the easiest way would be to add additional variable and inside update() assing to it our result of test().

As far as computed_owed_money() is concerned, our {#if } wrapper, that takes care of updating the content of the panel is inside FormPanel.svelte.

Yep, those components update like inside a tree. From top App.svelte to bottom Balance.svelte.

To compute our owed money without this {#if ...} for example when you want pure CSS hide-and-show, the easiest way to achieve it is to use stores API directly.

Remember how those $ sign worked right. Now, time to be come a bit pourer and loose few dollars.


import { pollStore } from "./globals.js";

$: average_amount = ($pollState.poll.amount / $pollState.people.length);

function compute_owed_money(people) {
    let acc = 0;
    for ( let i = 0; i < people.length; i++ ) {
        let diff = (average_amount - people[i].amount);
        if ( diff > 0 ) {
            acc += diff 
        }
    }

    return acc;
}


let owed_money = compute_owed_money($pollStore.people);

let unsub_owed = pollStore.subscribe(store => {
    owed_money = compute_owed_money(store.people);
})

Here we go. We use things hidden behind syntactic sugar normally.

You can read more about it on svelte.dev store API docs about writable.

Every time, our store will change, it will issue call to function inside pollStore.subscribe, and pass store value as argument to this call.

Quick talk about CSS

I am not going to put all this CSS here. What is actually important is that, you can get with Svelte3, the CSS modules for free. It will automatically add special class with hash-like names and generate proper CSS code in single bundle.

Summary

I learned a lot from this project and gained pretty big respect for what Svelte3 gives you for:

  • free
  • faster
  • and without setting up webpack which is my little personal horror.

My components are small, fast. Whole code feels like written in almost raw JS and not with use of any compiler/framework/unicorn.

Still, this is very much beginner project and I will love to try Svelte on bigger stuff.

What impressed me, was very little final size.
Whole bundle of javascript weighted around 60kB even with development stuff enabled and without minification.

React with everything given by default, full-minifaction in dev-build weighted 5 times more. Maybe the sizes are not large, smaller than most of the images.
The difference is: image can be fastly rendered on screen. JS can run any computation at all. And they all take time/processor/heat-up your phone and you are both get some pour sod in small village somewhere a little bit more angry than necessary and you add up a little bit of sand to the climate change.

Svelte feels like a better Vue. I think I will try to reimplement this or some similar project in Vue. I have some more ideas to try out.

What do you thing about this series? Did you like it? What were too hard to understand or weirdly written? What would you change? I would love to hear from you.

Goodbye, and have a nice day.

CRUD in Svelte3 (3 Part Series)

1) How to build Budget Poll App in Svelte3 2) Building Svelte 3 Budget Poll App [2] 3) Budget Poll App from Scratch in Svelte 3

Posted on by:

Discussion

markdown guide
 
 

Of course it is. As almost always I messed up order of URL elements in Markdown.

Thanks for heads up.