Managing state in Svelte

Joshua Nussbaum - Feb 26 '20 - - Dev Community

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

. . . . . . . . . . . . . . . . . . .