I am writing a Sveltekit app with Svelte 5 after taking a course from Niklas Fischer. The pattern is as follows: Mostly all state is contained in a global State class. In routes/+svelte.layout.ts
the State class is instantiated and then put in a context with setContext
. In various +page.svelte files the State is conveniently obtained with state = getContext(key)
. Quite often the State is destructured, e.g. let { statevar1, statevar2 } = $derived(state)
. It must be $derived
to work, just destructuring the state does not work, nor destructuring $state(state)
. I can understand whatlet doubled = $derived(count*2)
means, but here we are not changing the state variables, and worse, we may not change them later in the code, as you may not assign to derived values.
I wrote a small test to find out what works and what not. In test.svelte.ts
class Counter { cnt = $state(1); constructor(start: number) { this.cnt = start; } inc() { console.log('inc before', this.cnt); this.cnt += 1; console.log('inc after', this.cnt); } init() { console.log('init before', this.cnt); this.cnt = 100; console.log('init after', this.cnt); }}export const counter1 = new Counter(10);export const counter2 = $state({ cnt: 50});
In +page.svelte
<script lang="ts"> import { counter1, counter2 } from './test.svelte'; let { cnt: cnt1a } = counter1; let { cnt: cnt1b } = $state(counter1); let { cnt: cnt1c } = $derived(counter1); let { cnt: cnt2a } = counter2; let { cnt: cnt2b } = $state(counter2); let { cnt: cnt2c } = $derived(counter2);</script><h1>Counter1</h1><h1>{counter1.cnt} works</h1><h1>{cnt1a} fails</h1><h1>{cnt1b} fails</h1><h1>{cnt1c} works</h1><button onclick={() => counter1.init()}>init1 works</button><button onclick={counter1.init}>init2 fails</button><button onclick={() => counter1.inc()}>inc1 works</button><button onclick={counter1.inc}>inc2 fails</button><h1>Counter2</h1><h1>{counter2.cnt} works</h1><h1>{cnt2a} fails</h1><h1>{cnt2b} fails</h1><h1>{cnt2c} works</h1><button onclick={() => counter2.cnt++}>inc2</button>
When this page runs, the buttons init2
and inc2
do not work, because class methods can not be passed as function arguments (what a pity, I assume this is due to JS, not Svelte). With init1
and inc1
only the counter counter1.cnt
is reactive, and the derived counter cnt1c
. But cnt2b
is not, as I had assumed. And cnt1a
is also not reactive, which indicates that destructuring strips off the reactivity. Why is all that so?
In additon: In the class I must initialize with some value, which is immediately overwritten by the constructor. And I must initialize with $state()
, but I must not use $state in the constructor, i.e. I may not write this.cnt=$state(start)
. I have not even tried to call new Counter()
with a $stated variable...