Bigi Lui
2020-08-26 ⋅ 4 min read

Svelte Reactivity and Stores

I don't code in Svelte for my day job, so I only get to work with it on my hobby projects. Like most people, I only occasionally have time to work on it. I will have an ongoing project that I just randomly take a hiatus from because life, and then a month, a few months pass by, or maybe a year later, I get back to work on it.

Svelte is an intuitive frameowrk and once you learn the basics, it's easy to navigate a Svelte codebase and get things done. Occasionally there are quirks that catch you off guard, though.

One of the things that have caught me multiple times now, after taking a break and coming back to a project, is how Svelte handles reactivity with its Stores.

Let's say you have some Writable Stores like this, set up in a stores.js file:

import { writable } from 'svelte/store';

export const status_message = writable('Howdy');
export const game_state = writable({});
export const cards = writable([]);

You use these stores normally in your other Svelte components. So you have things like this:

<script>
  import { status_message } from './stores';

  status_message.set('Hello!');
</script>

<div>{$status_message}</div>

All good so far. Let's say though in some cases you put an object or an array into a Writable Store, like the example above with game_state and cards. Because usually you intend to update only some subset of the fields within the object, you may write your code like this in a component that updates it:

<script>
  import { game_state } from './stores';

  $game_state.current_turn = 2;
</script>

<div>Current turn: {$game_state.current_turn}</div>

And you'll find that game_state does not seem to be updated, even though you're using it as an Auto-subscribed Store by virtue of using $.

You have to use .set()

The gotcha here is that reactivity is triggered by the .set() call, not simply the data in the Writable Store being updated. So the way to do something like the example above would be:

<script>
  import { game_state } from './stores';

  $game_state.current_turn = 2;
  game_state.set($game_state);

  // Alternatively, use .update() instead of .set()
</script>

<div>Current turn: {$game_state.current_turn}</div>

When you think about the internal workings of Svelte, it makes sense; the specific functions of .set() or .update() on a Writable Store is what kicks off re-evaluation and reactivity based on the value of that store.

When I'm just coding though, I tend to think of a Store as simply a variable that can be accessed globally from anywhere in the frontend app and that changes to it immediately reflect everywhere else. Because of that, I tend to forget what I really should do to kick off a reactive update, especially when what's in the Store is an object or array.