{
"$type": "site.standard.document",
"content": {
"$type": "at.markpub.markdown",
"flavor": "gfm",
"renderingRules": "markdown-it",
"text": {
"$type": "at.markpub.text",
"markdown": "> Svelte version at time of writing: [svelte@5.0.5](https://github.com/sveltejs/svelte/releases/tag/svelte%405.0.5)\n\nOk so Svelte 5 has [officially been launched](https://svelte.dev/blog/svelte-5-is-alive) (aka out of beta) a few days ago and the [official migration](https://svelte.dev/docs/svelte/v5-migration-guide) guide followed shortly after. It was kind of a running joke among my peers that while in beta, the Svelte 5 documentation was a bit “sparse”. Like, only a few months ago we didn’t know for sure when to use `$effect` or how to use `$derived` when dynamically fetching data with `async` functions… Or how to replicate the ergonomics of file-based stores without using stores in Svelte 5.\n\nAlso, I ain’t gonna lie: I was rather shocked when `$runes` were first demo-ed with `getters` and `setters` and `Class`-based approaches and it took me some time to accept the new reality. I already started to miss the simple days of `$: just_do_it_and_dont_make_me_think`.\n\nTurns out that a lot has happened since then and I want to share some of the simple / simplified patterns that I’m currently using in production (partly since May/June, partly only recently).\n\nI’d say that Svelte 5 is actually easier and absolutely mightier than ever. One part of “mightier” is that it’s indeed far more predictable which was also part of the reasoning behind Svelte 5.\n\nAnyhow: Let’s start.\n\n> Disclaimer: I collected some of the learnings and patterns here from trying to make SvelteKit 2 prerender and SSR with Svelte 5 as perfectly as (economically) reasonable for elections live coverage. One core challenge for using prerendering and SSR with live data is that you need to provide an initial state prerenderable to a set of components, and later on update them all frequently in a consistent and reliable matter.\n\n## 1. On a Side-Note: Mixing Svelte 4 / 5\n\nActually totally do-able off the shelf. Even in SvelteKit. Close to zero effort.\n\nSometimes you might need some helpers such as [`fromStore`](https://svelte.dev/docs/svelte/svelte-store#fromStore) (`svelte/store`) or `$effect` to update tweened store values (see below). But other than that, just use them together - esp. if you’re not in the position to fully migrate to Svelte 5 right now.\n\n## 2. Bye bye Stores: Simple Shared State without Getters / Setters\n\nYes, getters / setters and Class (probably) totally have their uses.\n\nIt’s just that I’m only an ok-ish JavaScript developer and I totally got spoiled by Svelte 3’s simplicity. At least as of now (following an intense election-sprint-driven year), I can’t be bothered to write boilerplate-y code just to mimic what syntactic sugar around stores enabled me to do with 1-liners in the past:\n```javascript\nimport { store } from $stores/data`;\n\n// read\nlet param = $store\n\n// write\n$store = value\n```\nI just want reactive shared state.\n\nInstead I ended up producing semi-valid slop like this, using a getter but not using setters:\n```javascript\n// store.svelte.js\nconst counter = () => {\n let _count = $state(0)\n\n return {\n get count() {\n return _count\n },\n increment() {\n _count += 1\n },\n decrement() {\n _count -= 1\n },\n reset(value = 0) {\n _count = value\n }\n }\n}\n\nexport const count = counter()\n```\nWell, I just don’t `get` it - at least for now.\n\nMoving forward, the future of `svelte/store` also isn’t [entirely clear, it seems](https://www.reddit.com/r/sveltejs/comments/1g8toie/what_do_you_think/). And working with them in Svelte 5 feels a bit awkward (because you might need helpers like `fromStore(store)`) and again it feels boilerplate-y.\n\nPlus: Runes are cool. Runes are better (e.g. granular even on more complex Objects). The `$derived.by(function)` rune allows me to do stuff I didn’t even try to learn with the derived store.\n\nSo right now, I’m using two ~~lazy~~ ~~lean~~ minimalist patterns to share state between components and scopes:\n\n- file-based with `.svelte.js` universal reactivity (I continue to call them “stores”)\n- context-based with `setContext` / `getContext` and 1 trick\n\n### File-based shared runed state with .svelte.js files a.k.a. “Svelte 5 Stores”\n\n#### “readable”: simple read-only export\n\n- create the runed store\n```javascript\n// data.svelte.js\n// in this case it doesn't matter if it's const or let\nexport const store = $state(13)\n```\n- import and consume in component\n```javascript\n// Component.svelte\n<script>\n import { store } from '$stores/data.svelte.js'\n</script>\n\nvalue: {store}\n```\n> 💡 trying to write to / update / assign to the “store” will throw an error:\n```javascript\nCannot assign to import\n```\n#### “writable”: export an Object\n\n- create the runed store\n```javascript\n// data.svelte.js\nexport const store = $state({value: 13})\n```\n- import and consume in component\n```javascript\n// Component.svelte\n<script>\n import { store } from '$stores/data.svelte.js'\n</script>\n\nvalue: {store.value}\n```\n- update / write\n```javascript\nstore.value = new_value\n```\n- update / write by invoking a closure (incl. `store.value++` and so on)\n```javascript\n<button onclick={() => (store.value = new_value)}>++</button>\n```\n- you can actually somewhat conveniently add mock methods (or dare I say: “setters”), by adding self-referential closures to the state object\n```javascript\nexport const store = $state({\n value: 13,\n increase: () => store.count++\n})\n```\n- you can now invoke the function / “method” / “setter”\n```javascript\n<button onclick={count.increase}>++</button>\n```\n- However, notice how the closure explicitly references the exported object, as `this`-based approaches don’t seem to work (yes, I’ve read [MDN on this](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this)). If you know how to make `this` work without rocket science (or getters / setters), please let me know! Is it something about `Proxy`?\n```javascript\nexport const store = $state({\n value: 13,\n increase: () => store.count++,\n \n // doesn't work; logs the invoking element\n decrease() {\n console.log(this)\n this.value--\n },\n \n // doesn't work; logs undefined (as can be expected)\n update: () => {\n console.log(this)\n this.value--\n },\n \n // doesn't work; logs the invoking element\n test: function () {\n console.log(this)\n this.value--\n }\n})\n```\nWorks for me and is easy to memorize.\n\nWe’ll see how that will turn out in future versions of Svelte. Also, I must admit that I haven’t tested complex / nested Objects and the impact of this approach on the granularity of the reactivity. If I learn something new, I will update this post.\n\nCheck out the [example REPL](https://svelte.dev/playground/53f1e3c3009442cb87d783b485b70712?version=5.0.5).\n\n> 💡 I will write about Class-based runed state sharing or using getters / setters when I’m more comfortable with them.\n\nFinal note: I actually put the `.svelte.js` files (can we just continue to call them stores?) in the same folder where I used to put Svelte 3/4 stores: `src/lib/stores` aliased as `$stores`, `$state` or `$data`.\n\n### Shared runed state via getContext / setContext (and Closures)\n\nYou’re probably familiar with that pattern: sharing reactive state between related (!) components without a file-based store, or without passing and binding props three levels deep.\n\n> There’s actually quite a good use case for this pattern which involves the Singleton-nature of ES/UMD-modules and mounting the same compiled (!) module multiple times within the same window/document - but that’s for another blog post.\n\n#### Old getContext / setContext Pattern with Svelte 3/4\n\n- set the context in a parent / higher-level component\n```javascript\n// Parent.svelte\n\n<script>\n import { writable } from \"svelte/store\"\n import { setContext } from \"svelte\"\n import Child from \"./Child.svelte\"\n \n const store = writable(data)\n setContext(\"store\", store)\n</script>\n```\n- get the context in a related (as in a family tree) component\n- consume as a store (`$store = value` etc.)\n```javascript\n// Deeply_nested_child.svelte\n<script>\n import { getContext } from \"svelte\"\n const store = getContext(\"store\")\n</script>\n\nstore: {$store}\n```\nWorked perfectly fine but will throw / used to throw (?) errors in Svelte:\n```javascript\nState referenced in its own scope will never update.\nDid you mean to reference it inside a closure?\n```\nTurns out, there is a set of approaches to make this pattern work again with Svelte.\n\n#### New Svelte 5 setContext / getContext Pattern\n\n> 💡 Please note, that in mixed Svelte 4 / 5 mode the child can remain in Svelte 4 mode and consume the store with the Svelte 3 / 4 approach (see [REPL](https://svelte.dev/playground/baf6833075f24a1f82aa60ab2725ff14?version=5.0.5)). \n> In pure Svelte 5 mode, it’ll throw an error\n```javascript\nstore is updated, but is not declared with $state(...).\nChanging its value will not correctly trigger updates (non_reactive_update)\n```\n#### “readable”: simple read-only context\n\n- similar to the Svelte 3 /4 approach, define your runed store / `$state` in the upper parent scope\n- note how the context is set as a closure\n```javascript\n<script>\n let data = $state(12)\n import { setContext } from \"svelte\"\n\n setContext(\"store\", () => data)\n</script>\n```\n- consume in Child /deeper nested component by invoking the closure (!)\n```javascript\n<script>\n import { getContext } from \"svelte\"\n let store = getContext(\"store\")\n</script>\n\nvalue: {store()}\n```\n#### “writable”: export an Object\n\n- very similar to the file-based store approach, define the state as an Object, so that it gets proxied\n- here, we don’t need a closure as we pass the proxied Object (by reference, I guess)\n```javascript\n<script>\n const data = $state({value: 12})\n import { setContext } from \"svelte\"\n\n setContext(\"data \", data )\n</script>\n```\n- to consume, just read / access the value\n```javascript\n<script>\n import { getContext } from \"svelte\"\n let store = getContext(\"store\")\n</script>\n\nvalue: {store.value}\n```\n> 💡 you can add methods / handlers in the same way as we did for the file-based runed stores\n\nCheck out the [example REPL](https://svelte.dev/playground/868588bdb3e8446a9fcc400934327db4?version=5.0.5).\n\n> update (2024-10-25): `$derived` works as well, using the closure approach:\n```javascript\nconst derived_store = $derived(readable * writable.value);\nsetContext(\"derived_store\", () => derived_store)\n```\n## 3. Local State: $derived reconciliation\n\n- assume you have a component with an initial value (e.g. passed as `$props()`) e.g. for prerendering or to provide a state before the user interacts\n- later the value is going to change, coming from a different place (such as a centralized data polling function in a shared store, a user action and so on)\n- we’re gonna use `$derived.by` with a reconciliation if-else check to pick the value to be displayed\n- Empty state in store:\n```javascript\n// state.svelte.js\nexport const external_state = $state({value: null})\n```\n- reconciliation logic in component\n```javascript\n<script>\n import {external_state} from \"./state.svelte.js\"\n \n const initial_value = 12\n \n const local_state = $derived.by(() => {\n /** caution: 0 is falsy, value could be a Promise and so on...\n * this check might need more sophistication depending in the context\n */\n if (external_state.value) {\n return external_state.value\n } else {\n return initial_value\n }\n })\n</script>\n```\nSee the approach in action in the [example REPL](https://svelte.dev/playground/bdfc6542cc524ee5afa62a29e0da29b2?version=5.0.5).\n\n## 4. Svelte 5, Runes and Tweened Stores\n\n- so let’s say you have a tweened store for a numeric value you want to tween when some runed states updates\n- `pnpx sv migrate svelte-5` resulted in some `run()` from `svelte/legacy` package which just didn’t look right\n- Instead, I suggest to use `$effect` to update the tweened store value.\n```javascript\n// Component.svelte\n<script>\n import {tweened} from \"svelte/motion\"\n\n const tweened_value = tweened(0)\n\n let input_state = $state(3)\n\n $effect(() => {\n // tweened_value.set(input_state)\n $tweened_value = input_state\n })\n</script>\n```\nSee [example REPL](https://svelte.dev/playground/d945a5ab5e2443a7a351e1eeae6b6ccd?version=5.0.5) with a slider.\n\n---\n\nThat’s it for this part. I hope this is helpful to others. My next posts might be about proper SvelteKit prerendering or using Storybook with SvelteKit in library / package mode.\n\nLet me know what you think or correct me where I’m wrong:\n\n- Email: contact ( at ) fubits (dot) dev\n- Bluesky: <https://bsky.app/profile/fubits.dev>\n- Twitter (duh): <https://x.com/fubits>\n\nBye.\n"
}
},
"description": "I’ve been using Svelte 5 since mid May and learned some smart Svelte 5 patterns - some of which don’t even appear in the docs (at least as of today). Here are some of them.",
"path": "/notes/svelte-5-patterns-simple-shared-state-getcontext-tweened-stores-with-runes/",
"publishedAt": "2024-10-17T00:00:00.000Z",
"site": "at://did:plc:6aglx53tojyuwxwueap5og3h/site.standard.publication/self",
"tags": [
"Svelte",
"Svelte5"
],
"textContent": "Svelte version at time of writing: svelte@5.0.5\n\nOk so Svelte 5 has officially been launched (aka out of beta) a few days ago and the official migration guide followed shortly after. It was kind of a running joke among my peers that while in beta, the Svelte 5 documentation was a bit “sparse”. Like, only a few months ago we didn’t know for sure when to use $effect or how to use $derived when dynamically fetching data with async functions… Or how to replicate the ergonomics of file-based stores without using stores in Svelte 5.\n\nAlso, I ain’t gonna lie: I was rather shocked when $runes were first demo-ed with getters and setters and Class-based approaches and it took me some time to accept the new reality. I already started to miss the simple days of $: just_do_it_and_dont_make_me_think.\n\nTurns out that a lot has happened since then and I want to share some of the simple / simplified patterns that I’m currently using in production (partly since May/June, partly only recently).\n\nI’d say that Svelte 5 is actually easier and absolutely mightier than ever. One part of “mightier” is that it’s indeed far more predictable which was also part of the reasoning behind Svelte 5.\n\nAnyhow: Let’s start.\n\nDisclaimer: I collected some of the learnings and patterns here from trying to make SvelteKit 2 prerender and SSR with Svelte 5 as perfectly as (economically) reasonable for elections live coverage. One core challenge for using prerendering and SSR with live data is that you need to provide an initial state prerenderable to a set of components, and later on update them all frequently in a consistent and reliable matter.\n\nOn a Side-Note: Mixing Svelte 4 / 5\n\nActually totally do-able off the shelf. Even in SvelteKit. Close to zero effort.\n\nSometimes you might need some helpers such as fromStore (svelte/store) or $effect to update tweened store values (see below). But other than that, just use them together - esp. if you’re not in the position to fully migrate to Svelte 5 right now.\n\nBye bye Stores: Simple Shared State without Getters / Setters\n\nYes, getters / setters and Class (probably) totally have their uses.\n\nIt’s just that I’m only an ok-ish JavaScript developer and I totally got spoiled by Svelte 3’s simplicity. At least as of now (following an intense election-sprint-driven year), I can’t be bothered to write boilerplate-y code just to mimic what syntactic sugar around stores enabled me to do with 1-liners in the past:\n\nimport { store } from $stores/data;\n\n// read\nlet param = $store\n\n// write\n$store = value\n\nI just want reactive shared state.\n\nInstead I ended up producing semi-valid slop like this, using a getter but not using setters:\n\n// store.svelte.js\nconst counter = () => {\nlet _count = $state(0)\n\nreturn {\nget count() {\nreturn _count\n},\nincrement() {\n_count += 1\n},\ndecrement() {\n_count -= 1\n},\nreset(value = 0) {\n_count = value\n}\n}\n}\n\nexport const count = counter()\n\nWell, I just don’t get it - at least for now.\n\nMoving forward, the future of svelte/store also isn’t entirely clear, it seems. And working with them in Svelte 5 feels a bit awkward (because you might need helpers like fromStore(store)) and again it feels boilerplate-y.\n\nPlus: Runes are cool. Runes are better (e.g. granular even on more complex Objects). The $derived.by(function) rune allows me to do stuff I didn’t even try to learn with the derived store.\n\nSo right now, I’m using two lazy lean minimalist patterns to share state between components and scopes:\n\nfile-based with .svelte.js universal reactivity (I continue to call them “stores”)\ncontext-based with setContext / getContext and 1 trick\n\nFile-based shared runed state with .svelte.js files a.k.a. “Svelte 5 Stores”\n\n“readable”: simple read-only export\n\ncreate the runed store\n\n// data.svelte.js\n// in this case it doesn't matter if it's const or let\nexport const store = $state(13)\n\nimport and consume in component\n\n// Component.svelte\n<script>\nimport { store } from '$stores/data.svelte.js'\n</script>\n\nvalue: {store}\n\n💡 trying to write to / update / assign to the “store” will throw an error:\n\nCannot assign to import\n\n“writable”: export an Object\n\ncreate the runed store\n\n// data.svelte.js\nexport const store = $state({value: 13})\n\nimport and consume in component\n\n// Component.svelte\n<script>\nimport { store } from '$stores/data.svelte.js'\n</script>\n\nvalue: {store.value}\n\nupdate / write\n\nstore.value = new_value\n\nupdate / write by invoking a closure (incl. store.value++ and so on)\n\n<button onclick={() => (store.value = new_value)}>++</button>\n\nyou can actually somewhat conveniently add mock methods (or dare I say: “setters”), by adding self-referential closures to the state object\n\nexport const store = $state({\nvalue: 13,\nincrease: () => store.count++\n})\n\nyou can now invoke the function / “method” / “setter”\n\n<button onclick={count.increase}>++</button>\n\nHowever, notice how the closure explicitly references the exported object, as this-based approaches don’t seem to work (yes, I’ve read MDN on this). If you know how to make this work without rocket science (or getters / setters), please let me know! Is it something about Proxy?\n\nexport const store = $state({\nvalue: 13,\nincrease: () => store.count++,\n\n// doesn't work; logs the invoking element\ndecrease() {\nconsole.log(this)\nthis.value--\n},\n\n// doesn't work; logs undefined (as can be expected)\nupdate: () => {\nconsole.log(this)\nthis.value--\n},\n\n// doesn't work; logs the invoking element\ntest: function () {\nconsole.log(this)\nthis.value--\n}\n})\n\nWorks for me and is easy to memorize.\n\nWe’ll see how that will turn out in future versions of Svelte. Also, I must admit that I haven’t tested complex / nested Objects and the impact of this approach on the granularity of the reactivity. If I learn something new, I will update this post.\n\nCheck out the example REPL.\n\n💡 I will write about Class-based runed state sharing or using getters / setters when I’m more comfortable with them.\n\nFinal note: I actually put the .svelte.js files (can we just continue to call them stores?) in the same folder where I used to put Svelte 3/4 stores: src/lib/stores aliased as $stores, $state or $data.\n\nShared runed state via getContext / setContext (and Closures)\n\nYou’re probably familiar with that pattern: sharing reactive state between related (!) components without a file-based store, or without passing and binding props three levels deep.\n\nThere’s actually quite a good use case for this pattern which involves the Singleton-nature of ES/UMD-modules and mounting the same compiled (!) module multiple times within the same window/document - but that’s for another blog post.\n\nOld getContext / setContext Pattern with Svelte 3/4\n\nset the context in a parent / higher-level component\n\n// Parent.svelte\n\n<script>\nimport { writable } from \"svelte/store\"\nimport { setContext } from \"svelte\"\nimport Child from \"./Child.svelte\"\n\nconst store = writable(data)\nsetContext(\"store\", store)\n</script>\n\nget the context in a related (as in a family tree) component\nconsume as a store ($store = value etc.)\n\n// Deeply_nested_child.svelte\n<script>\nimport { getContext } from \"svelte\"\nconst store = getContext(\"store\")\n</script>\n\nstore: {$store}\n\nWorked perfectly fine but will throw / used to throw (?) errors in Svelte:\n\nState referenced in its own scope will never update.\nDid you mean to reference it inside a closure?\n\nTurns out, there is a set of approaches to make this pattern work again with Svelte.\n\nNew Svelte 5 setContext / getContext Pattern\n\n💡 Please note, that in mixed Svelte 4 / 5 mode the child can remain in Svelte 4 mode and consume the store with the Svelte 3 / 4 approach (see REPL).\nIn pure Svelte 5 mode, it’ll throw an error\n\nstore is updated, but is not declared with $state(...).\nChanging its value will not correctly trigger updates (non_reactive_update)\n\n“readable”: simple read-only context\n\nsimilar to the Svelte 3 /4 approach, define your runed store / $state in the upper parent scope\nnote how the context is set as a closure\n\n<script>\nlet data = $state(12)\nimport { setContext } from \"svelte\"\n\nsetContext(\"store\", () => data)\n</script>\n\nconsume in Child /deeper nested component by invoking the closure (!)\n\n<script>\nimport { getContext } from \"svelte\"\nlet store = getContext(\"store\")\n</script>\n\nvalue: {store()}\n\n“writable”: export an Object\n\nvery similar to the file-based store approach, define the state as an Object, so that it gets proxied\nhere, we don’t need a closure as we pass the proxied Object (by reference, I guess)\n\n<script>\nconst data = $state({value: 12})\nimport { setContext } from \"svelte\"\n\nsetContext(\"data \", data )\n</script>\n\nto consume, just read / access the value\n\n<script>\nimport { getContext } from \"svelte\"\nlet store = getContext(\"store\")\n</script>\n\nvalue: {store.value}\n\n💡 you can add methods / handlers in the same way as we did for the file-based runed stores\n\nCheck out the example REPL.\n\nupdate (2024-10-25): $derived works as well, using the closure approach:\n\nconst derived_store = $derived(readable * writable.value);\nsetContext(\"derived_store\", () => derived_store)\n\nLocal State: $derived reconciliation\n\nassume you have a component with an initial value (e.g. passed as $props()) e.g. for prerendering or to provide a state before the user interacts\nlater the value is going to change, coming from a different place (such as a centralized data polling function in a shared store, a user action and so on)\nwe’re gonna use $derived.by with a reconciliation if-else check to pick the value to be displayed\nEmpty state in store:\n\n// state.svelte.js\nexport const external_state = $state({value: null})\n\nreconciliation logic in component\n\n<script>\nimport {external_state} from \"./state.svelte.js\"\n\nconst initial_value = 12\n\nconst local_state = $derived.by(() => {\n/** caution: 0 is falsy, value could be a Promise and so on...\nthis check might need more sophistication depending in the context\n*/\nif (external_state.value) {\nreturn external_state.value\n} else {\nreturn initial_value\n}\n})\n</script>\n\nSee the approach in action in the example REPL.\n\nSvelte 5, Runes and Tweened Stores\n\nso let’s say you have a tweened store for a numeric value you want to tween when some runed states updates\npnpx sv migrate svelte-5 resulted in some run() from svelte/legacy package which just didn’t look right\nInstead, I suggest to use $effect` to update the tweened store value.\n\n// Component.svelte\n<script>\nimport {tweened} from \"svelte/motion\"\n\nconst tweened_value = tweened(0)\n\nlet input_state = $state(3)\n\n$effect(() => {\n// tweened_value.set(input_state)\n$tweened_value = input_state\n})\n</script>\n\nSee example REPL with a slider.\n\nThat’s it for this part. I hope this is helpful to others. My next posts might be about proper SvelteKit prerendering or using Storybook with SvelteKit in library / package mode.\n\nLet me know what you think or correct me where I’m wrong:\n\nEmail: contact ( at ) fubits (dot) dev\nBluesky: https://bsky.app/profile/fubits.dev\nTwitter (duh): https://x.com/fubits\n\nBye.",
"title": "Svelte 5 Patterns (Part 1): Simple Shared State, getContext, and Tweened Stores with $runes",
"updatedAt": "2024-10-23T00:00:00.000Z"
}