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}/>
When any prop changes, the component is automatically re-rendered.
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>
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}/>
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}/>
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/>
and, in the descendant component:
<!-- Descendant.svelte -->
<script>
import {getContext} from 'svelte'
// read data from Context
const {value} = getContext('MY_KEY')
</script>
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(...)
}
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>
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)
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 ?
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...
What a great article! Just one thing carefully explained with simple pictures and code snippets. This is gold!
Santa Svelte bless you! short, sharp, precise, perfect article, thanks!
Amazing article 🤩
Many thanks for the illustrations and code snippets 👍
Those diagrams really cleared up the concepts about Svelte state management. 😺
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?
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 tosetContext
are stores. Example:Yes, very important point about context not being reactive. For that reason I almost never use it.