Published on

State Management In Svelte

Ever wondered how to manage state in Svelte applications? Sure its easy to do so in popular web frameworks such as React.js and Angular.js.

Svelte Logo

Figure 1: Svelte Logo

Svelte Context API

Overview

If you're used to React and its concept of 'props' then the Svelte Context API maybe vaguely familiar to you. However, unlike React props the Svelte Context API does not require that you pass around data and functions as props but rather it is a way for components to communicate with each other without the aforementioned hinderances.

The Svelte Context API consists of two primary methods: setContext() and getContext(). As the name might suggest, setContext() is used to set some data in the form of key-value pairs. Ex: setContext("fruit", "apple"). Then, getContext() can retrieve a given value using its corresponding key like so: getContext("fruit").

It is important to keep in mind that the Context API must be called during component initialization and not any time afterwards. For example, calling it in the beforeUpdate() or afterUpdate() methods would throw an error.

Keys in setContext()

Although the Context API seems similar to localStorage of the window interface, they differ because setContext() allows for non-string objects to be used as keys. It is common to use a Symbol() instance to represent keys when using the Context API. Using Symbol() instead of strings have the benefit of guaranteed uniqueness whereas keys defined by strings have the possibility to conflict with each other because of uniqueness issues. Here is an example involving Symbol():


import { setContext } from 'svelte';

const key1 = Symbol()
const key2 = Symbol()
setContext(key1, "value1");

Svelte Store

Although the Svelte Context API is great for small applications, as applications scale up it becomes increasingly difficult to manage state in a simple and concise way. For this we have Store, a global store of data that is indifferent to the component hierarchy of the application. This is because data set in one component can be accessed by any other component, regardless of its place in the component hierarchy.

It is important to note that Store is not unique to Svelte and can be used in any JavaScript application.

Let's see an example of how data can be get and set using Store in Svelte:

import { Store } 'svelte/store.js';

const store = new Store({
    day: 'Monday'
});

const { day } = store.get(); // 'Monday'

On top of getting and setting data, Store can use its on() method as an event listener for various contexts such as:

this.store.on('state', ({ current }) => {
    console.log(current.day); // 'Monday'
});

this.store.on('destroy', ({ current }) => {
    console.log(current.day); // 'Monday'
});

Now at this point if you're familiar with Redux or Vuex you might be thinking that Store is identical to those popular state container systems. However there is an important difference that separates Store from the rest: Store doesn't have the concepts of actions and reducers. In case you're unclear of what actions are: they are JavaScript objects that contain data in order to act as the sole source of information for the store and act as carriers for data from the application to the store(more on Redux actions here). Redux commits can be defined as. Also a bit on Redux reducers: these are functions that take in the current state and an action as input and return a new state as a result. Think of them as calculators, taking input and spitting out results.

Due to the reasons mentioned above Store in Svelte is not as strict and constrictive as React's Redux and Vue's Vuex. The user has more say in how to use Store, which can be a good or bad depending on the context in which it is used.

There are two main types of Svelte stores: writable and readable. Let's go over them briefly

Writable stores

Writable stores are basically sources of information storage, otherwise known as stores, that allows external components to set data within it.

In order to use the writable store in Svelte we will have import the writable() function as shown in this example:

<script>
	import { writable, get } from "svelte/store";

	let fruit = writable("apple");

	console.log("The fruit is an " + get(fruit)); // "The fruit is an apple"
</script>

Notice that the state value for the fruit variable is set via the writable() method and its value is retrieved via the get() method. The writable() method also takes a second argument like so:

<script>
	import { writable, get } from "svelte/store";

	let fruit = writable("apple", () => {
            console.log("Got one subscriber");
            return () => console.log("Deleted subscriber");
        });

	const firstSub = fruit.subscribe(value => {
  	    console.log(value);
        });

	firstSub();
</script>

The function passed in as the second argument serves as a callback for when a subscription to the fruit writable variable occurs. Therefore, with the initialization of the firstSub subscription, the message "Got one subscriber" will be printed to the console. Then when the firstSub() method is called, that subscription will be lost and therefore the second function, which is declared in the return statement, will be executed. This is would print the following result to the console:

"Got one subscriber"
"apple"
"Deleted subscriber"

Readable stores

Readable stores differ from writable stores in that values cannot be set by outside/external components. Some practical uses for readable stores include stores that represent a user's payment information or keystroke history, as in these use cases it would only be a detriment to allow external components to modify those state values.

Here is an example of how the readable store can be used:

import { readable } from "svelte/store";

const initialData = {id: '123', firstname: 'First Name', lastname: 'Last Name'};

export const user = readable(initialData, (set) => {
    const userApiInterval = setInterval(() => {

    fetch('https://randomuser.me/api/').then((data) => data.json()).then((user) => {
        const {first, last } = user.results[0].name;
        const {value} = user.results[0].id;
        const newUser = {
            id: value,
            firstname: first,
            lastname: last
        }
        set(newUser);
	console.log(user);
        }).catch((ex) => {
            set(null);
        })

    }, 10000);

    return () => clearInterval(userApiInterval)
});

The above user writable store makes API calls to the https://randomuser.me/api/ API endpoint and sets the response as the new value for the initialData variable. Finally the new value is logged out to the console.

Disadvantages of State Management in Svelte

Due to the popular opinion that state management in Svelte is not as robust and developed as in React, Svelte's system of data binding is frequently criticized. This is because instead of having a single source of truth(more on that here), Svelte lets various pieces of state spread out throughout the application. Let's go through an example to explain this point further:

<script>
	   let fruits = ['apples', 'bananas', 'mangos'];

    const addFruit = (fruit) => {
        fruits = [...fruits, fruit];
	console.log(fruits);
    };
</script>

<button onClick={addFruit('grapefruit')} >
	Add Grapefruit
</button>

In the example above, clicking the button appends an extra item to the fruits array. Now consider that we want to categorize the fruits according to four types of fruits: citrus, stone, berries, melons like so:

<script>
    let fruits = {
        citrus: ["oranges", "madarins", "lime"],
        stone: ["necatrines", "apricots", "peaches"],
        berries: ["strawberries", "raspberries", "blueberries"],
        melons: ["watermelons", "rockmelons", "honeydew melons"]
    };
...
</script>
...

Having changed the fruits array to an object , we now have to update this change wherever fruits in being used. This can cause all sorts of data inconsistency issues and therefore this method data binding should be avoided when possible.

Svelte is definitely not as strict or framework driven about state management as React. Also, since Svelte is a relatively new framework it does not have as much documentation on its various APIs and features as it should have. So keep these points in mind when trying it out for the first time.

Conclusion

Whew! If you made it this far, congrats! You now know how Svelte state management works!

Although there are much more details about Svelte's state management that weren't covered in this blog post, for the sake of brevity let us end it here. Hopefully you now have a better idea of the how the Svelte framework manages state.

Thanks for reading this blog post on Svelte State Management!

If you have any questions or concerns please feel free to post a comment in this post and I will get back to you when I find the time.

If you found this article helpful please share it and make sure to follow me on Twitter and GitHub, connect with me on LinkedIn and subscribe to my YouTube channel.