Svelte 5 signals fix its glitchy and inconsistent reactivity
Svelte 5 signals fix its glitchy and inconsistent reactivity
 Aug 15, 2024  Svelte is a frontend JS framework that's known in part for
			its terse and ergonomic reactivity. Reactivity is how changes to data synchronize with the UI
			and other downstream data. In 2019 Svelte 3 introduced a reactivity design that caught a lot of attention for its ease and apparent simplicity. Svelte rapidly rose to prominence
			in its crowded space, and in surveys it has consistently been well-liked by its users. But 5 years later, Svelte 5 is again rethinking its reactivity, this time with signals and runes syntax - why? I thought we loved
			it? To many, Svelte 3 was a fresh and attractive answer to the hard problem of building reactive
			UIs. It chose plain HTML as its starting point, and added some of the simplest possible syntax
			to make its semantics reactive. Few frameworks look better in trivial examples: <script>
  let count = 1;
</script>

<button on:click={() => count++}>
  {count}
</button> This terse and fairly efficient reactivity was one of the main reasons I switched to Svelte
			full-time in 2019 after 5 years of React. But while its reactivity looks simple on the
			surface, problems appear in nontrivial cases. There be dragons beyond this inviting facade. This post describes two specific problems with Svelte 3's reactivity and how they're fixed in
			Svelte 5 with signals, the primitive behind its runes. Svelte 5 fixes many other reactivity
			issues, and also improves its composability and other aspects, but we'll focus on two things: reactive statement inconsistency derived store glitches  Svelte 5, largely inspired by Solid, uses signals to
			improve its reactivity and fix these two problems, among others. In 2024 signals are being
			adopted by most popular frontend frameworks, React being the notable exception with its purely
			functional roots. Signals are popular enough to have a broadly-backed proposal to standardize them in the JS language.  Inconsistent reactive statements Inconsistency is a term I'm borrowing from Ryan Carniato (see this video for example) that describes the situation where you update the dependencies of a derived value
			(in this case, derived via a reactive statement), and then synchronously read the derived value,
			but you get the old value. For example: let a = 1;
$: b = a * 2;
setTimeout(() => {
	console.log(b); // 2
	a = 3;
	console.log(b); // 2 ???
});
 REPL It seems logical that b would be 6 after reassigning a to 3, not still 2. The problem is the reactive statement starting
			with $: does not re-run immediately - its update is deferred to the next tick of the JS runtime. The same
			problem causes b to be undefined during initialization: let a = 1;
$: b = a * 2;
console.log(b); // undefined ???
 REPL Svelte 5 fixes this problem with runes, which use signals under the hood: let a = $state(1);
const b = $derived(a * 2);
console.log(b); // 2 :)
setTimeout(() => {
	a = 3;
	console.log(b); // 6 :)
});
 REPL This behavior of reactive statements wasn't unthoughtful design. It avoids numerous issues
			that result from eager evaluation, and it was an exploration of the limits of static analysis,
			which was a promising direction to improve the ergonomics and efficiency of reactive systems,
			as Svelte creator Rich Harris discusses in this video. Signals straightforwardly resolve this wicked problem of reading stale derived values by
			consistently having the latest value.  Glitchy derived stores In reactive programming, glitches occur when a program computes temporary values that make no sense. For example if you have fullname that combines a firstname and lastname, an example glitch might
			involve computing a full name using the first and last names of two different people, even
			when the code updates variables sequentially: // pseudo-code
firstname = 'Alan'
lastname  = 'Turing'
fullname  = firstname + ' ' + lastname
firstname = 'Barbara' // does code see 'Barbara Turing' here? if so it's a glitch
lastname  = 'Liskov'
 Glitches can be a source of: bugs: what happens if you make an API call with a nonsense value? or will your frontend code
				error when encountering them? who knows! I hope your tests are thorough complexity: your code may have to deal with strange values that needn't exist waste: glitchy calculations are by definition immediately invalidated, so they're pure waste
				- this can affect the UX with many updates or expensive calculations Derived stores have glitches: const firstname = writable('Barbara');
const lastname = writable('Liskov');
const fullname = derived([firstname, lastname], ([$firstname, $lastname]) => {
	const $fullname = $firstname + ' ' + $lastname;
	console.log('derived fullname', $fullname);
	return $fullname;
});
$fullname; // logs "Barbara Liskov"
$firstname = 'Alan'; // glitch - logs "Alan Liskov"
$lastname = 'Turing'; // logs "Alan Turing"
 REPL Signals are one reactivity system that can avoid glitches, depending on the implementation[1]. Svelte 5 and the proposed standard are glitch-free.  One important detail here is that signals do allow reading intermediate values. If you read fullname in between the two updates, you will indeed see the same value as the glitch. This makes sense,
			because it means it's consistent as described above with reactive statements. The key point is
			that unlike derived stores, signals do not see or calculate intermediate values by default - they
			can but it's under your control. Explicitly pulling an intermediate value is not a glitch. Also, reactive statements are incapable of observing intermediate values, a limitation that
			can necessitate some painful restructuring in rare but valid cases. Signals give us the best of both worlds, with the eager observability of derived stores, and
			the glitch-free efficient batching of reactive statements. They afford not only fine-grained performance, but also fine-grained control[2].  A complete example This REPL compares Svelte 3 reactive statements, derived stores, and Svelte
			5 signal-based runes. These three different reactive systems have subtle but important differences.  Here are the key lines of code being compared, rewritten to remove the logging noise needed
			for the demo: Reactive statements: $: ab = a + ' ' + b; Derived stores: const ab = derived([a, b], ([$a, $b]) => $a + ' ' + $b); Signals: const ab = $derived(a + ' ' + b);  Things to notice in the REPL: the final rendered output in the DOM is what we expect, leading many people to be unaware of
				the lurking problems (e.g. clicking "swap uppercase and lowercase" never displays A b or a B) reactive statements are undefined during initialization - this is inconsistent reactive statements have stale values when read synchronously after writes to their
				dependencies - click the swap button and see how the after swap values are the
				same as before swap for reactive statements, unlike the other two examples - this
				is inconsistent derived stores calculate and notify glitchy values like A b, a B,
				and they also wastefully recompute immediately after each dependency change, even when more
				changes to other dependencies are synchronously incoming the signals-based $derived rune has neither of these problems With both the reactive statements and derived stores, your code receives unexpected values in
			some cases, whereas signals always give you the latest value and they update predictably.
			Though subtle, these details can sometimes be critically important. Signals sidestep glitches while providing a consistent and more optimal reactivity model. From easy to simple Engineering is all about tradeoffs, and as Rich Harris said in this interview, Svelte before version 5 was overly focused on the first 5 minutes of the developer
			experience. Svelte 3's apparent simplicity is full of subtle complexity. The cost of easy was
			unexpectedly high, and part of that cost was paid in simplicity for nontrivial usage. This
			post covers only a small fraction of Svelte 3's complexities. If you're curious about the
			details you could start with these issues.  Svelte 3 has two different reactive systems - component-local reactivity (including reactive
			statements) and stores (including derived) - each with its own problems, and where the former
			system is available only inside .svelte files. This creates two worlds where interactions
			between them can be surprising, reasoning through the details is challenging, and refactoring code
			between them is tedious and error-prone. In 2019 I rationalized this by thinking I could select the better system for the problem at
			hand, but I eventually felt that neither system is great, and the combination is unwelcomed
			complexity. The Svelte team concluded similar and did some serious engineering to isolate the
			unavoidable complexities behind friendly APIs. Svelte 5 has universal reactivity across both JS/TS modules and Svelte files instead of
			splitting the world in two, and runes, which leverage Svelte's compiler architecture, further
			sweeten signals syntax. I could continue listing the wins in Svelte 5's design, but I think
			the most important points are the improved reactivity model, universality, and composability
			(maybe a future post). The UX improves too with better performance, not to mention the
			likelihood of more productive and happier developers. I've experienced many of the pain points of the previous design, and now having spent months
			with the pre-release of Svelte 5, I can confidently say the relief it brings is significant.
			My code is easier to read and write, runes guide me to better decision making, and its
			performance characteristics are pushing the state of the art. I'm looking forward to many more
			years of using Svelte with solid productivity and good vibes. Footnotes      Comments  