|
my personal blog

Svelte reactivity in production

January 19, 2025

While the Svelte documentation is great and handles explaining the fundamentals, I noticed I had to figure out some intricacies myself with practice. This post covers some cases that came up while using Svelte in production. We use toy examples here for simpler explanations. This post is directed towards those already familiar with Svelte basics and this is specifically for Svelte 3 & 4.

1. Reactivity in functions

I’ll walk through a few cases that come up with functions that will probably save you a few hours of debugging.

Functions will use the current value of any variables defined in the component, but you need to be careful when updating those variables within the function.

<script>
    let a = 1;
    $: b = a * 2;

    // problem: we expect the b to print 6 since `a` is updated to 3, but it doesn't!
    function setAAndPrintB() {
        a = 3;
        console.log(b);  // b will print 2 here
		console.log(a);
    }
</script>

<button on:click={setAAndPrintB}>button</button>

playground link

This happens because the reactive block runs in a different synchronous task.

What happens if try to fix this by making the function reactive?

<script>
    let a = 1;
    $: b = a * 2;  // this assignment depends on a

    // Problem: Invalid function definition because of circular dependency
    $: setAAndPrintB = () => {
        a = 3;
        console.log(b);  // this function depends on b  
		console.log(a);
    }
</script>

<button on:click={setAAndPrintB}>button</button>

playground link

If we move the dependency (variable b) to a separate function then we will have our fix.

<script>
    let a = 1;
    $: b = a * 2;

    function printB() {
		console.log(b);
	}
	
	function setAAndPrintB() {
		a = 3;

        // kicks off a microtask which allows svelte's reactive assignment of b to run after a is set.
		Promise.resolve().then(() => {
			printB(); // will print 6 like we want
		});
	}
</script>

<button on:click={setAAndPrintB}>button</button>

playground link

We take advantage of using promises to queue javascript microtasks to make sure Svelte’s internal state is updated before we print b.

One final note about functions: components won’t re-render if the function isn’t declared reactively in some way.

On clicking the button, b updates but we don’t see text within the div.

<script>
	let a = 1;
    $: b = a * 2;

	function renderB() {
		return b;
	}
</script>

<div>{renderB()}</div> <!-- problem: we expect this to update when `a` changes but it doesn't! -->
<button on:click={() => a += 2}>button</button>

playground link

If instead we declare the function reactively, we’ll see the updates displaying properly. That’s because we marked the function assignment of renderB value as reactive so svelte will re-run it whenever b changes. Since the <div> contents is dependent on that function it will also re-render the <div> contents.

<script>
	let a = 1;
    $: b = a * 2;

	$: renderB = function() {
		return b;
    }
</script>

<div>{renderB()}</div>  <!-- updates whenever we click the button! -->
<button on:click={() => a += 2}>button</button>

playground link

2. Svelte stores for controlled reactivity

Svelte stores can be used to get a little bit more fine grained reactivity. If for some reason you only want a reactive block to run once, even if a value changes then using a svelte store is a good idea. The store doesn’t change—its value does. For example, initialization code that’s dependent on variable a may only want to run once.

<script>
	import { writable } from "svelte/store";
	import Render from "./Render.svelte";
	
    let a = writable(1);
</script>

<button on:click={() => a.update(a => a + 1)}>button</button>
<Render {a} />  <!-- Reactive blocks won't re-run when the stores value changes -->

playground link

Here without using the store we see that the console.log prints each time you click the button.

<script>
	import Render from "./Render.svelte";
	
    let a = 1;
</script>

<button on:click={() => a += 1}>button</button>
<Render {a} />

playground link

3. Ordered dependencies

Suppose you want to make sure a network request is made before some logic is run. You can track this with a variable and then check for a true value in a reactive if block.


<script>
	let requestMade = false;
	let display = "";

	$: if (requestMade) {
		display = "Hello!";
	}
</script>

<div>{display}</div> <!-- only renders have button is clicked -->
<button on:click={() => requestMade = true}>Button</button>

playground link

Sometimes a cleaner approach is to use multiple components. This way if you have a chain of dependencies it’s easy to reason about.


<script>
	let requestMade = false;
    let secondRequestMade = false;

    $: if (requestMade && secondRequestMade) {
        console.log("Two requests made");
    }
</script>

{#if requestMade}
    <Hello bind:secondRequestMade >

{/if}
<button on:click={() => requestMade = true}>Button</button>

I think this last note is obvious when you spell it out in a toy example, but I found that in practice sometimes it’s tricky to realize there is a dependency between two components, and an interaction would become easier to code with some order of operations.

Conclusion

Hopefully this was somewhat useful. I’ll add some edits here if I come across more interesting examples that come up in practice. I know Svelte 5 tackles all of these in some way or another, but it’s still new so for a production app, I think we plan on giving it some time before migrating over.