Logo

dev-resources.site

for different kinds of informations.

Svelte 5: Share state between components (for Dummies)

Published at
1/2/2025
Categories
svelte
javascript
webdev
state
Author
mandrasch
Categories
4 categories in total
svelte
open
javascript
open
webdev
open
state
open
Author
9 person written this
mandrasch
open
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("");
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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: [] });
Enter fullscreen mode Exit fullscreen mode

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!"}; 
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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} />
Enter fullscreen mode Exit fullscreen mode
<!-- 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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} />

Enter fullscreen mode Exit fullscreen mode
<!-- SearchInput.svelte -->

<script>
    let {stateObjPropToChange = $bindable() } = $props();
</script>

<label>
   Search text:
   <input bind:value={stateObjPropToChange} />
</label>
Enter fullscreen mode Exit fullscreen mode

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: []});
Enter fullscreen mode Exit fullscreen mode
<!-- App.svelte -->
<FilterCheckboxes 
    title="Colors" 
    availableOptions={availableColorOptions} 
    bind:statePropToBind={selectedColorsState.selectedValues}
/>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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

Demos:

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)!

svelte Article's
30 articles in total
Favicon
🚀 I have released Eurlexa!!! EU Regulation at Your Fingertips!
Favicon
Building "Digital DSA": The Journey of Crafting a Smarter Loan Comparison Platform
Favicon
Tower defense clicker game built with Svelte 5, without canvas. Only CSS transitions and the power of Runes
Favicon
Optimize SvelteKit performance with brotli compression
Favicon
Migrating the VSCode theme generator to oklch
Favicon
Beatbump: Exploring Svelte Best Practices for Dynamic Web Applications
Favicon
SvelteJs vs ReactJs : But both can co-exists!
Favicon
Build a Simple Chatbot with Svelte and ElizaBot
Favicon
A minimalist password manager desktop app: a foray into Golang's Wails framework (Part 2)
Favicon
The Perfect Trio: Wails, Go & Svelte in Action
Favicon
Svelte 5: Share state between components (for Dummies)
Favicon
Integrating SvelteKit with Storyblok (Using Svelte 5)
Favicon
Changelog 2024/48 aka We 🖤 Svelte 5
Favicon
My first Micro SaaS | Automated, SEO-friendly Changelogs
Favicon
Quit Using Anonymous Functions in Props!
Favicon
From Svelte 4 to Svelte 5: Understanding Slots (default and named)
Favicon
Make EditorJS work in Svelte(kit) SSR
Favicon
Shadcn UI Theme generator, with OKLCH colors and ancient sacred geometry.
Favicon
A short guide to Async Components in Svelte 5
Favicon
New Post here!🍻
Favicon
Svelte vs Vue: Choosing the Best Front-End Framework
Favicon
Step-by-Step Tutorial: How to Use ALLAIS for Effortless UI Generation
Favicon
Schritt-für-Schritt-Anleitung: So verwenden Sie ALLAIS für die einfache UI-Generierung
Favicon
ALLAIS: Your Ultimate Personal UI Generator for Modern Web Development
Favicon
Пошаговый учебник: Как использовать ALLAIS для легкой генерации UI
Favicon
Svelte
Favicon
A minimalist password manager desktop app: a foray into Golang's Wails framework (Part 1)
Favicon
When Goliath Fell to David’s Smallest Stone: The Innocent Dropdown Tweak That Shattered React, Vue, and Svelte
Favicon
Deploying Your SvelteKit App to GitHub Pages with a Custom Domain (Ditch GoDaddy for Porkbun)
Favicon
Syncing DOM State and Data without IDs

Featured ones: