Understanding Assignment and Reactivity in Svelte
Tags: svelte, javascript, webdev
Published: 2023-01-30 10:00:00

_Svelte's reactivity is built into the language_ as this [post](https://svelte.dev/blog/svelte-3-rethinking-reactivity) noted. Despite limited experiece with svelte yet, I feel more comfortable working with reactivity in svelte than in other frontend frameworks I've used so far.

In this post, I want to share a question I had on the topic and my effort to find the answer.

---

## Table of Contents

- [My Question](#my-question)
- [Assignment With Side-Effect](#assignment-with-side-effect)
- [$$invalidate and Indirect Assignment](#--invalidate-and-indirect-assignment)
- [Conclusion](#conclusion)

---

## My Question

When I first picked up [svelte](https://svelte.dev/) a month ago after working with React for a few years, I made a simple todo app to play with.

```javascript
<script>
  let todos = [
    {id: 0, name: 'buy milk', done: false},
    {id: 1, name: 'buy food', done: false}
  ]

  function toggle() {
    // TODO: implement toggle
  }
</script>

<ul>
{#each todos as todo (todo.id)}
  <li>
    Name: {todo.name}  Done: {todo.done}
  </li>
{/each}
</ul>

<button on:click={toggle}>Toggle</button>
```

I tried implementing two versions of the `toggle` function as the following and was surprised to see the different outcomes produced by them when I hit the button:

```javascript
// version 1
function toggle() {
  todos.forEach((todo, i) => {
    todo.done = true
    console.log(todos)
  })
}

// version 2
function toggle() {
  todos.forEach((_, i) => {
    todos[i].done = true
    console.log(todos) 
  })
}
```

![Output for toggle version 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lr63b0v4pb0f4gzvlbt.png)

<figcaption>Output for toggle version 1</figcaption>

<br>

![Output for toggle version 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/thelvoyqbgeg05v9dz3f.png)

<figcaption>Output for toggle version 2</figcaption>

<br>

Obviously, both `toggle`s updated the data but only version 2 triggered an update of the UI, in which case we say, in the frontend development world, the app is _reactive_ to `toggle` version 2 but not version 1.

Why?

---

## Assignment With Side-Effect

To answer the question, I realized I had first to understand how reactivity works in svelte.

According to the [docs](Svelte's reactivity is based on assignments), _svelte's reactivity is based on assignments_. But what are the implications of this? Both of my implementations of the `toggle` function include an assignment statement and a further check indicated that both `todo` in version 1 and `todos[i]` in version 2 point to the same object as expected:

```javascript
function toggle() {
  todos.forEach((todo, i) => {
    console.log(todo === todos[i]) // => true
  })
}
```

This explained why the data, `todos`, was correctly updated in both cases as the console outputs showed and made it clear that there must have been a _side-effect_(as svelte creator [Rich Harris](https://twitter.com/Rich_Harris) mentioned in [this video](https://www.youtube.com/watch?v=AdNJ3fydeao)) coming along with the version 2 of `toggle` that triggered a re-rendering of the UI.

---

## $$invalidate and Indirect Assignment

One thing I love about svelte most is the highly interactive [REPL](https://svelte.dev/repl/hello-world?version=3.55.1). It makes playing around with the code much easier and of much more fun.

I checked out the compiled JS of both versions of the `toggle` function and found they were not nearly the same:

```javascript
// version 1: compiled
function toggle() {
  todos.forEach((todo, i) => {
    todo.done = true;
  });
}

// version 2: compiled
function toggle() {
  todos.forEach((_, i) => {
    $$invalidate(0, todos[i].done = true, todos);
  });
}
```

The alien-looking `$$invalidate` function in the second snippet turned out to be the source of all the magic. It is the side-effect generated by assignment that triggers an update of the screen.

You don't need to know the implementation details of `$$invalidate` to understand reactivity in svelte, but for those who are curious, feel free to take a look at this amazing [post](https://lihautan.com/compile-svelte-in-your-head-part-1/) by svelte's maintainer [Tan Li Hau](https://twitter.com/lihautan) before diving deep into the [source code](https://github.com/sveltejs/svelte/blob/739bfaec1285e9be1301f9dbc26551c79c61c2d8/src/compiler/compile/render_dom/invalidate.ts#L8) of svelte.

Now, the question becomes why did one assignment generate `$$invalidate` and another fail to? The answer has something to do with _indirect assignment_.

In the [official tutorial](https://svelte.dev/tutorial/updating-arrays-and-objects), it's pointed out that _indirect assignments won't trigger reactivity_.

Just as this code, used in the tutorial, won't trigger reactivity:

```javascript
const foo = obj.foo;
foo.bar = 'baz';
```

The first implementation of `toggle` won't either, because fundamentally, it is an indirect assignment:

```javascript
// version 1
function toggle() {
  todos.forEach((todo, i) => {
    todo.done = true
  })
}

// is equivalent to
function toggle() {
  todos.forEach((_, i) => {
    const todo = todos[i]
    todo.done = true
  })
}
```

Svelte checks if the updated variable or any of its properties is assigned a new value to determine whether `$$invalidate` should be generated. This is how reactivity in svelte is implemented under the hood. If you wonder HOW it is implemented, again, go ahead and read its docs.


Based on what we've found, if I'm so stubborn as to insist on using the first version of `toggle` but want all the reactive magic to happen at the same time, I can just add a legitimate assignment like the following to generate the `$$invalidate` side-effect:

```javascript
// version 1
function toggle() {
  todos.forEach((todo, i) => {
    todo.done = true
    todos = todos // add this line
  })
}
```

`todos[i] = todos[i]` also does the trick:

```javascript
// version 1
function toggle() {
  todos.forEach((todo, i) => {
    todo.done = true
    todos[i] = todos[i] // this works too
  })
}
```

---

## Conclusion

The answer to my question turned out to be that the code, which failed to trigger a re-rendering of the UI, had an indirect assignment and indirect assignment does not trigger reactivity in svelte.

We looked at the `$$invalidate` side-effect which is the source of reactive magic behind svelte. We realized that to make a variable reactive, we must assign a value to itself or its properties if the variable is an object, otherwise `$$invalidate` won't be generated.