dev-resources.site
for different kinds of informations.
Svelte 5: Share state between components (for Dummies)
As soon as you see the new $state
in Svelte 5, you might be tempted to do this:
// sharedState.svelte.js
// This won't work as export! ❌
export const searchState = $state("");
<!-- App.svelte -->
<script>
import { searchState } from './sharedState.svelte.js'
function handleClick(){
// This won't work! ❌
searchState = "bicycles";
}
</script
<button onclick={handleClick}>Search for bicycles</button>
The above doesn't work if you export it - and here is why:
„You're encountering the most complicated part of Svelte 5. How reactivity works and how the compiler hides it from you.
When you export a single value like a number or string, there is no mechanism for Svelte to maintain reactivity because JavaScript doesn't offer a way to track that.“
Mat Simon
Huge thanks to Mat Simon who explained this to me in a Bluesky thread 💡
Here is what I learned so far:
For exports, use $state with Objects - not Strings!
As said, we can't use a String (or a Number) directly for exporting states like we usually do in a single file component.
// sharedState.svelte.js
// This won't work as export! ❌
export const searchState = $state("");
export const countState = $state(0);
But, Objects in $state() get all their property values proxied automatically by Svelte v5. Wrapping your String value into an Object allows you to export that state and share it between files and components:
// sharedState.svelte.js
// Instead of this ...
// export const searchState = $state("");
// ... do this:
export const searchState = $state({ text : "" });
// Instead of this ...
// export const countState = $state(0);
// ... do this:
export const countState = $state({ count : 0 });
With that searchState
and countState
are turned into an Svelte state proxy with a getter and a setter for the .text
property.
When you import a $state Object, and then update the property via .text = "newValue"
, you're using the Svelte setter to update the state. This will then be updated in all other places where the state is used:
// App.svelte
import { searchState } from './sharedState.svelte.js'
function handleClick(){
// uses the automatically created setter
searchState.text = "bicycles";
}
<button onclick={handleClick}>Search for bicycles</button>
Demo (REPL): Very basic $state example
You can choose any property name you want, as well as multiple properties per $state Object. Svelte 5 takes care of it automagically.
// number
export const countState = $state({ count : 999 });
// multiple properties (number, string)
export const anotherState = $state({ id: 123, title: "Hello World!" });
// array
export const tagsState = $state({ selectedTags: [] });
Full technical background explained by Mat Simon:
Svelte doesn't have to proxy ever method that modifies the underlying object. That would be pretty error prone. Every time JavaScript adds a new method, the Svelte team would need to adapt immediately, otherwise reactivity would break for that specific method. In reality JavaScript proxies are pretty cool. They offer get() and set() traps. [..] This means that the actual implementation of the array (and object) proxy is quite simple and handles all possible methods the array implements and might implement in the future. See Svelte docs for more details.
The Svelte docs state:
If $state is used with an array or a simple object, the result is a deeply reactive state proxy.
The official tutorial for this is Universal Reactivity.
Beware: Don't re-assign Objects outside of their scope
When you use an Object they are also not states themselves! That's important to understand. If you do the following after an import, you lost reactivity:
import { searchState } from './sharedState.svelte.js';
// don't do this!
searchState = {text: "Hello world!"};
There is no way for Svelte to handle this for you. Always use the automatic Svelte getter/setter for exports/imports via
searchState.text = 'new value';
Note: In the same scope, if Objects are defined with $state(), they can be overridden. This article is only about exports.
Advanced: Use classes
There are multiple options to define your state objects, you can also use classes if you ever needed custom methods for updating values in your state: https://joyofcode.xyz/how-to-share-state-in-svelte-5#using-classes-for-reactive-state
Share state between components
So we know how to import (and update) states inside components and we know that we can use objects out of the box with $state
:
// MyComponent.svelte
import { searchState } from './sharedState.svelte.js'
function handleClick(){
searchState.text = "bicycles";
}
<button onclick={handleClick}>Search for bicycles</button>
We can even pass down the $state object as reference by a property with $props:
// App.svelte
<script>
import { searchTextState } from './data.svelte.js';
import ResultList from './ResultList.svelte';
</script>
<ResultList stateObj={searchTextState} />
<!-- ResultList.svelte -->
<script>
// reference to state object is passed down as prop of component
let {stateObj} = $props();
</script>
<p>You're searching for {stateObj.text}</p>
But how do you know that the state changed somewhere in your app when you're inside a component and want to do some processing based on that value? That's what $derived and $derived.by are for:
<!-- ResultList.svelte -->
<script>
// reference to state object is passed down as prop
let {stateObj} = $props();
// Listen for state changes
let resultString = $derived.by(() => {
console.log('state change detected', {stateObj});
// we would filter results here, do custom stuff
// for now, we just mess with the search text
let currentText = stateObj.text;
let uppercaseText = currentText.toUpperCase();
return `You are searching for ${uppercaseText}`;
});
</script>
<p>You're searching for {resultString}</p>
Simple Demo (REPL): Share $state between components (simple)
Usage with bind:value
As you might already know, there is no need to write change handler functions for text inputs. You can just use bind:value
to update the state automatically when text is entered:
<!-- App.svelte -->
<script>
import { searchTextState } from './data.svelte.js';
<script>
<SearchInput bind:stateObjPropToChange={searchTextState.text} />
<!-- SearchInput.svelte -->
<script>
let {stateObjPropToChange = $bindable() } = $props();
</script>
<label>
Search text:
<input bind:value={stateObjPropToChange} />
</label>
Make sure to use bind:
when passing the prop to the component and use $bindable() when receiving the prop in the component.
Usage with bind:group
Multiple checkbox inputs can be handled with Svelte via bind-group=
.
To use bind:group with shared state, make sure to use bind:
when passing the prop to the component:
// state.svelte.js
export const selectedColorsState = $state({selectedValues: []});
<!-- App.svelte -->
<FilterCheckboxes
title="Colors"
availableOptions={availableColorOptions}
bind:statePropToBind={selectedColorsState.selectedValues}
/>
And make sure to use $bindable() when receiving the prop in the component:
<!-- FilterCheckboxes.svelte -->
<script>
let {
title,
availableOptions,
// important: you need to receive a bindable here,
// to update states back to parent components
statePropToBind = $bindable()
} = $props();
</script>
- Simple Demo (REPL): Search and filter with checkbox group components and v5 $state & $derived
But beware, the [Svelte docs for bind:group] state:
bind:group only works if the inputs are in the same Svelte component.
If you have two components with different values for selection, bind-group
won't work. You should split this into separate states. See bind:group does not work with nested components #2308 for more details.
Disclaimer: If you want to use filter checkboxes with URL params (query parameters) in SvelteKit, it might be easier to derive the global state from page.url.searchParams
via import {page} from
$app/stateand
$derived`. See https://github.com/mandrasch/svelte-nobel-prize-demo for more details.
Summary
There is a big difference between using $state()
inside one file (one scope) - or using $state()
as export / import.
Happy to get your (critical) feedback on this article! 🙏
More resources
- Bluesky discussion: #Svelte v5 - is this the easiest way to use $state (and $derived) with multiple checkboxes to filter data dynamically? 🤔
- Different Ways To Share State In Svelte 5 - Joy of Code + YouTube
- Lets Build A Filtering System with Svelte 5 , Sveltekit 2, Tailwind, Upstash (2024) - Lawal Adebola
- Svelte 5 - Global $state (convert stores to $state runes) - Svelte Mastery
- Svelte 5 Runes Demystified (1/4) - Signal Reactivity Basics - Peter Makes Websites Ltd
- Nerd discussion: I’ve been championing Svelte for 3+ years, and runes are killing me.
- Official tutorial: Universal reactivity
- Bonus: Understanding Effects In Svelte 5 And When To Use Them
Demos:
- Very basic $state example
- Share $state between components (simple)
- Search and filter with checkbox group components and v5 $state & $derived (WIP)
- Full SvelteKit example: https://github.com/mandrasch/austrian-web-dev-companies (WIP)
Advanced: Use SvelteSet, SvelteMap, SvelteDate, etc.
Okay, objects are fine and handled by Svelte automagically - we got it.
But what about Date
, URL
and more built-in objects of standard JavaScript? And if you're more experienced in JavaScript, you might know that there are some more advanced data types (standard built-in objects):
The
Set
object lets you store unique values of any type, whether primitive values or object references.The
Map
object holds key-value pairs and remembers the original insertion order of the keys.
If you want to use these with reactive $state, you need to use their corresponding Svelte wrapper from svelte/reactivity
- MediaQuery
- SvelteDate
- SvelteMap
- SvelteSet
- SvelteURL
- SvelteURLSearchParams
The reason there is a separate SvelteSet
and SvelteMap
class (instead of just rewriting it automatically like they do with objects and arrays) is because they wanted to draw a line somewhere since they can't proxy every conceivable object. See Reactive Map, Set, Date and URL #10263 for technical details.
How can you use it? Simple as that:
svelte
// sharedState.svelte.js
import { SvelteSet } from 'svelte/reactivity'
export const selectedColors = new SvelteSet(['red'])
But beware: If you want to output a SvelteSet
, make sure to use this (or use the new [$inspect])(https://svelte.dev/docs/svelte/$inspect):
{JSON.stringify({selectedColors: [... selectedColors]})}
Acknowledgements
Thanks very much to Mat Simon and hfcRed (Svelte Discord)!
Featured ones: