Svelte journey | Templating, Reactivity

chillyhill

Denys Sych

Posted on December 22, 2023

Svelte journey | Templating, Reactivity

Hello!

Svelte has been in my vision scope for quite some time, but it wasn't until today that I actually started exploring it. Finally, I decided to dig it further, triggered by another encounter with yet another post titled "Svelte changed my life". I bet a lot of people heard about it but didn’t start for some reason. Well, this article may be the one!

I may draw parallels to well-known approaches/tooling (mostly React or Angular) when applicable and interject with my own commentary.

Of course, I highly recommend referring to the official tutorial, it is great. However, my aim is to distill the most relevant information and incorporate some thoughts that crossed my mind, you may find this also helpful.

In the context of this series, our goal is to grasp the fundamentals of Svelte as a UI library and SvelteKit as a comprehensive toolkit that equips us with a mature set of tools to construct outstanding applications.

We'll commence with the Basics and delve into Reactivity in this article. You might have already heard about reactivity in Svelte, and it's truly amazing. Still, Svelte team prepares some updates regarding that (Runes, signals).

In this chapter, we embark on our journey together. So, let's delve in.

Templating and basic stuff

<script>
    import Nested from './Nested.svelte';
  let count = 0;

    function increment() {
        count += 1;
    }
</script>

<p>This is a paragraph.</p>

<Nested />

<button on:click={increment}>
    Clicked {count}
    {count === 1 ? 'time' : 'times'}
</button>

<style>
    p {
        color: goldenrod;
        font-family: 'Comic Sans MS', cursive;
        font-size: 2em;
    }
</style>
Enter fullscreen mode Exit fullscreen mode

It looks pretty much the same as the well-known JSX, so don't expect anything too spicy at this point.

However, there are some perks that I would like to outline:

Shorthand attributes

<img {src} alt="{name} dances." /> — no need for src={src}, just {src} is enough.

Code placement

Styles, code, and template in a single file. Code should be wrapped in <script />, and styles in <style />.

Handlers notation

<button on:click={increment}> — not onClick.

Plain string HTML embed

If you need to render HTML stored in a plain string, then use <p>{@html string}</p>. Keep in mind that there is no sanitization. Dangerous HTML stays dangerous 🔫.

Reactivity 💲

As mentioned at the beginning (and as everyone constantly emphasises), reactivity in Svelte is a pure gold. Still, some updates are coming in Svelte 5.

// Example of basic reactivity usage
<script>
  let count = 0;
  $: doubled = count * 2;                  // Kind of like a selector

  $: if (count >= 10) {                    // Kind of a Conditional Effect
    alert('Count is dangerously high!');
    count = 0;
  }

  $: console.log(`The count is ${count}`); // Kind of a one-line Effect

  function increment() {
    count += 1;
  }
</script>

<button on:click={increment}>
  Clicked {count} {count === 1 ? 'time' : 'times'}
</button>

<p>{count} doubled is {doubled}</p>

Enter fullscreen mode Exit fullscreen mode

Crucial parts, in my opinion, are as follows:

$ Reactive declarations (something like selectors)

let count = 0;
$: doubled = count * 2; // This code `$: doubled = count * 2` re-runs whenever any of the referenced values change.
Enter fullscreen mode Exit fullscreen mode

Why not use plain variables for the same purpose?
Variables (let, const) always keep the same initial constant values if you try to make them like $.

For example:

let a = 1;
let b = a * 2;
$: c = a * 2;
function foo() { a++ }
Enter fullscreen mode Exit fullscreen mode

When foo is called: a and c changes, but b remains the same. You can do assignment of b inside the function to change it, but that is another story, which we’ll get to in a minute.

Calling order

Reactive declarations and statements run after other script code and before component markup is rendered.

$ Reactive statements (looks like effects)

Automatic calls happen when the dependency is updated, which feels really good.

// one liner
$: console.log(`The count is ${count}`);
// group multiple lines
$: {
  console.log(`The count is ${count}`);
  console.log(`This will also be logged whenever count changes`);
}
// with operator (if)
$: if (count >= 10) {
  alert('Count is dangerously high!');
  count = 0;
}
Enter fullscreen mode Exit fullscreen mode

$ Reactive stuff and referential types

Mutations

push, splice, and similar mutation operations won't automatically cause updates. If you want to change something, then an assignment should happen.

// Ugly way
function addNumber() {
  numbers.push(numbers.length + 1);
  numbers = numbers; // Apply update via assignment
}
// [...pretty, way]
function addNumber() {
  numbers = [...numbers, numbers.length + 1];
}

Enter fullscreen mode Exit fullscreen mode

Assignments to properties of arrays and objects, e.g., obj.foo += 1 or array[i] = x, work the same way as assignments to the values themselves. So, as with plain old assignment for let or const, values inside properties can be modified without reassignment.

<script>
  let foo = { bar: 2 };
  let oof = foo;

  function handleClick() {
    foo.bar = foo.bar + 1; // This will update {foo.bar}
    foo.bar = oof.bar + 1; // This will also update {foo.bar}
    // Uncomment next line to make {oof.bar} be updated
    // oof = oof;
  }
</script>

<button on:click={handleClick}>
  foo.bar: {foo.bar} | oof.bar: {oof.bar}
</button>
Enter fullscreen mode Exit fullscreen mode

A simple rule of thumb: the name of the updated variable must appear on the left hand side of the assignment.

For example:

// Won't trigger reactivity on obj.foo.bar
// unless you follow it up with obj = obj.
const obj = { foo: { bar: 1 } };
const foo = obj.foo;
foo.bar = 2;
Enter fullscreen mode Exit fullscreen mode

By the way, you may be in the same situation as me: when I came across this code sample in the tutorial, I interpreted it as something like this:

<script>
  const obj = { foo: { bar: 1 } };
  const foo = obj.foo;
  foo.bar = 2;

  $: pluckedBar = obj.foo.bar

  obj.foo.bar = 100; // This still triggers reactivity
</script>

{pluckedBar} // Shows 100, but I had a feeling that it should stay
Enter fullscreen mode Exit fullscreen mode

After spending some time, I figured out that it is actually this:

<script>
  let obj = { foo: { bar: 1 } };
  let foo = obj.foo;
  $: pluckedBarBeforeReassign = foo.bar
  foo.bar = 2;
  $: pluckedBarAfterReassign = foo.bar

  function doit() {
    foo.bar = foo.bar + 1;
    /*
     * If commented, then {obj.foo.bar} in the template would never update.
     * The rest, {foo.bar} {pluckedBarBeforeReassign} {pluckedBarAfterReassign}
     * act as expected.
    */
    // obj = obj
  }
</script>

<button on:click={doit}>
  {obj.foo.bar} <!--This guy you should pay attention to-->
  {foo.bar}
  {pluckedBarBeforeReassign}
  {pluckedBarAfterReassign}
</button>
Enter fullscreen mode Exit fullscreen mode

Summary

In this part, we've covered the basics of templating and reactivity. We've followed the Svelte official tutorial, so if you feel excited about Svelte and want to have a broader look, it is a great idea to visit their interactive tutorial. My goal here is to share with you the most crucial information with extended comments on tricky parts, and I hope that I've accomplished that at least to some extent.

Take care, go Svelte!

💖 💪 🙅 🚩
chillyhill
Denys Sych

Posted on December 22, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related