DEV Community

Joshua Nussbaum
Joshua Nussbaum

Posted on • Updated on

Managing state in Svelte

A lot of what we do as developers is move state around.

We get state from the user, transform it, and pass it along to the server. Eventually we get some state back, transform it and then display it.

So, where is the right place to keep your state?

Svelte has multiple options depending on your needs, let's break them down:

Props

When you want to send state downward through the component tree, you want a prop. It can be either a variable or expression:

<Component prop1={someVar} prop2={a + b}/>
Enter fullscreen mode Exit fullscreen mode

When any prop changes, the component is automatically re-rendered.

Alt Text

Events

Events bubble state upward. This allows a child components to signal a state change to a parent component.

To do this, create a dispatcher dispatcher = createEventDispatcher(), and then produce an event by calling dispatch(eventName, eventData).

Here's an example:

<!-- Child.svelte -->
<script>
  import {createEventDispatcher} from 'svelte'

  // boilerplate required to produce events
  const dispatch = createEventDispatcher()

  // made up event handler
  function handleClick() {
    // fire event named 'message'
    dispatch('message', {data: ...})
  }
</script>
Enter fullscreen mode Exit fullscreen mode

and the parent component looks like this:

<!-- Parent.svelte -->
<script>
  // import the child component
  import Child from './Child'

  // event handler
  function handleMessage(data) {
    // do something interesting here :)
  }
</script>

<!-- wire up event handler for 'message' -->
<Child on:message={handleMessage}/>
Enter fullscreen mode Exit fullscreen mode

Alt Text

Data Binding

It's very common for a parent and child component to sync state. Sure, it can be accomplished with just props and events, ie. the child publishes an event, the parent handles the event, and updates a prop.

It's so common, that Svelte provides a declarative shortcut called "data binding"

Data binding syncs props in both directions, upwards & downwards, without event handling.

It works with any prop, simply add the bind: directive to the prop name.

Example:

<!-- anytime var1 or var2 changes, <Component> will be re-rendered -->
<!-- anytime prop1 or prop2 changes inside <Component>, var1 & var2 are updated -->
<Component bind:prop1={var1} bind:prop2={var2}/>
Enter fullscreen mode Exit fullscreen mode

Alt Text

Context

Props, events and data binding are sufficient for most situations.

But when you have a family of components that all share the same state, it can be tedious to pass the same props & events repeateadly.

For this situation, Svelte gives us Context, which is way for a root component to share state with all its descendants.

The root component creates the state with setContext('SOME_KEY', state), and then descendants can retrieve the state by calling getContext('SOME_KEY').

Example:

<!-- Root.svelte -->
<script>
  import {setContext} from 'svelte'

  // create context, MY_KEY is arbitrary
  setContext('MY_KEY', {value: 41})
</script>

<!-- notice, we don't need to pass props: -->
<Descendant/>
<Descendant/>
<Descendant/>
Enter fullscreen mode Exit fullscreen mode

and, in the descendant component:

<!-- Descendant.svelte -->
<script>
  import {getContext} from 'svelte'

  // read data from Context
  const {value} = getContext('MY_KEY')
</script>
Enter fullscreen mode Exit fullscreen mode

Alt Text

Stores

Not all state belongs in the component tree. Sometimes there are visually disconnected components sharing the same state.

Imagine an app with a logged in user. It would be tedious to pass the user= prop to every component. Many components would have to take the user= prop, just to pass it along because a grand-child or great-grand-child needed it.

This is where using a store makes sense, we can centralize the state of the user into a store. When a component needs user data, it can import it with import {user} from './stores'.

// stores.js
// export a user store
export user = writable({name: "Tom Cook"})

// export functions to access or mutate user
export function signOut() {
  user.update(...)
}
Enter fullscreen mode Exit fullscreen mode

And to use it:

<!-- pages/Dashboard.svelte -->
<script>
  import {user} from '../stores'
</script>

<!-- notice the "$",  that tells svelte to subscribe to changes in the store -->
<h1>Welcome back {$user.name}!</h1>
Enter fullscreen mode Exit fullscreen mode

Alt Text

LocalStorage

To persist state locally between visits, LocalStorage is your friend. Svelte doesn't provide any specific feature for this, but you can easily roll your own by building a custom store.

Here is an example: https://gist.github.com/joshnuss/aa3539daf7ca412202b4c10d543bc077

Summary

Svelte provides several ways to maintain state.

The most basic is keeping state in the visual tree.

Depending on the direction the state moves, you can use props, events, or data binding. When a family of components share state, use Context.

When state is used by many unrelated components, or to formalize access to data, use Stores.

Happy coding!

✌️

If you want to learn more about Svelte, check out my upcoming video course

Top comments (9)

Collapse
 
fpassx profile image
Frédéric Passaniti

And what about the use case when we want to persist the state "remotely" (in opposition with locally)
Using a base64 hash in the URL (GET Param) to sync your local state with the url, and be able to bookmark and share a link and reload your app where you left it ?

Collapse
 
joshnuss profile image
Joshua Nussbaum

For remote state, you can always use fetch or websocket.

A useful approach is to create a store that is backed with a remote API. So whenever the store changes, you push the changes to the remote API and vice versa.

Here's an example of an HTTP REST store that manages a singleton:
gist.github.com/joshnuss/e4c4a4965...

Collapse
 
zyumbik profile image
zyumbik

What a great article! Just one thing carefully explained with simple pictures and code snippets. This is gold!

Collapse
 
jm61 profile image
jm61

Santa Svelte bless you! short, sharp, precise, perfect article, thanks!

Collapse
 
bgrand_ch profile image
Benjamin Grand

Amazing article 🤩
Many thanks for the illustrations and code snippets 👍

Collapse
 
annietaylorchen profile image
Annie Taylor Chen • Edited

Those diagrams really cleared up the concepts about Svelte state management. 😺

Collapse
 
gkankava profile image
gkankava

Great article! Just one question, what's the difference between ContextAPI and stores. Like in both i don't need to map state to props, i can access then from any component, and mutate it?

Collapse
 
joshnuss profile image
Joshua Nussbaum

If you think of your UI components as a tree, context is state that is shared with a specific branch of the tree.

Whereas Stores are state that live outside the component tree.

Note: Context is not reactive. That means if we call setContext() multiple times, child components won't be notified of changes. So often the state passed to setContext are stores. Example:

const myStoreA = writable(...)
const myStoreB = writable(...)

setContext('myKey', {myStoreA, myStoreB })

// now when children call getContext() they will get stores that are reactive
Enter fullscreen mode Exit fullscreen mode
Collapse
 
mvolkmann profile image
Mark Volkmann

Yes, very important point about context not being reactive. For that reason I almost never use it.