Published: 14.01.2021 | Edited: 14.01.2021 | Tags: svelte

Using arrays in Svelte localStorage store

Difference between cookies and localStorage

LocalStorage allows browsers to store data related to the application in the client. This might seem indetical to the purpose of the cookies, but there are differences, otherwise it could just be called otherCookies instead of localStorage.

First, the localStorage is expected to synchronize over multiple tabs and windows, while in cookies, the behaviour varies across browsers and is generally not consistent. For inconsistencies I would blame the fact, that cookies are an older technology and were designed with different goal in mind.

Another difference is that cookies, if present, are attached to every user request. For a best user experience, it is preferred to keep the server-client data exchange at minimum. Although we live in a world where the connection speeds are ever-increasing, rular and remote areas tend to follow the suit at a much slower pace. Eliminating the unnecessary data from the round-trip helps a lot. Sending around megabytes with cookies every single request is definitely the way to go when building a slick app.

Cookies, unlike localStorage, have an expiry date. They are also targeted to purge by multiple forms of cleaning or privacy software. In such scenario, user could be happy by removing space on his device whilst simultaneously tackling privacy concerns only to learn that the work he done in your app is not saved anymore. Relying only on the localStorage to save user's work data is not a way to go either, because it might get purged when clearing browser cache.

Lastly, localStorage is not accessible directly on the server, only on the client. Cookies are accessible on the both sides. The API to handle either is thus also different. They are clearly meant for a different tasks.

Here's a simple comparison of the text above:

diffrence cookie localStorage
Expiration yes no
Accessibility server and client client only
Availability on every request manual access
Tabs synchronization varies across browsers built-in

Svelte and localStorage

Having the need to have persistent reactive data in the Svelte app I have decided to tackle the problem by starting off with an existing example from REPL.

In order to run the example you have to download it, as it would not run in the sandboxed environment. I have did so, so you do not need to. The example was not suitable for my application. It had two undesired features that I had to fix. First, at the very first load (or after a localStorage was cleared), the store contained null value. Secondly, it was not working with arrays.

For a reference, the example code of the store itself looked like this:

import { writable } from "svelte/store"

const storedTheme = localStorage.getItem("theme")
export const theme = writable(storedTheme)
theme.subscribe(value => {
  localStorage.setItem("theme", value === "dark" ? "dark" : "light")
})

First load

It took me a while to understand why the code was getting null even tough there is a default value ingrained in a form of a ternary operator:

value === "dark" ? "dark" : "light"

It became clear to me that method only concerns saving, not retrieving. When the localStorage is empty, the value retrieved is null, which is then passed into to writable store. The store's .subscribe method is thus called when we want to .set the value of the store, which too late in the lifecycle. The change had to be made when initializing a writable store, because it's first argument is where the store value initially comes from.

import { writable } from "svelte/store"

const itemName = "storedArray"
const retrieved = localStorage.getItem(itemName)
export const storedArray = writable(parsed === null ? [] : retrieved)

storedArray.subscribe(value => localStorage.setItem(itemName, retrieved))

This approach would work save for the one fact that surprised me when I have discovered for the first time:

localStorage does not support arrays, only strings

That's right - localStorage does not support data structures, only scalars. There are obvious ways around this limitation, once you are aware they exist. Discovering this was the tricky part, because browser did not throw any error trying to save array in the localStorage. Inspecting the localStorage contents via tools the brower provides displayed the values neatly delimited with a comma, resembling and array very closely.

Arrays in localStorage

Fortunately, this problem not a problem anymore. To convert a data structure to an string, we can serialize it. Common way to do it in JS is to convert it to JSON using JSON.stringify(). To revert it back, we use JSON.parse().

import { writable } from "svelte/store"

const itemName = "storedArray"
const retrieved = localStorage.getItem(itemName)
const parsed = JSON.parse(retrieved)
export const storedArray = writable(parsed === null ? [] : parsed)

storedArray.subscribe(value =>
  localStorage.setItem(itemName, JSON.stringify(value))
)

Now you can work with persistent store containing an array in your Svelte project as well. As a side note, since objects in JS can be serialized into JSON as well (because JSON is in fact JavaScript Object Notation, made specifically for this purpose), this approach can be modified to accomodate object in localStorage quite easily.

The code which again, due to sandboxing won't work straight in REPL or can be also cloned from the repository to make it run locally

Futher reading

  1. https://svelte.dev/docs#svelte_store
  2. https://chasingcode.dev/blog/svelte-persist-state-to-localstorage/
  3. https://www.tutorialspoint.com/What-is-the-difference-between-local-storage-vs-cookies
  4. https://stackoverflow.com/questions/3357553/how-do-i-store-an-array-in-localstorage
  5. https://stackoverflow.com/questions/57089227/inconsistency-when-writing-synchronous-to-localstorage-from-multiple-tabs