Rethinking client storage
Tags: javascript, computerscience, theorycrafting, svelte
Published: 2020-07-02 04:18:23

<span>Cover by <a href="https://unsplash.com/@redaquamedia?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Denny Müller</a> on <a href="https://unsplash.com/s/photos/hard-drives?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>
# Preface
I feel like `localStorage` is a bit of an under appreciated storage mechanism. I've got some ideas that I want to get on paper and this seemed like a great place. Because `localStorage` and `sessionStorage` only differ in their persistence we can assume all of these thoughts apply to both.

## Note
I am aware of IndexedDB however I'm certain this brain worm wont let me rest until I test my idea. 🤷 I would rather fail knowing than live wondering. 

## Common issues
The most common pitfalls that I seem to see with `localStorage` seem to be the following:
1. It only stores string values
2. The read/write operations are always synchronous

With that out of the way lets address the issues.

### Issue 1 - Handling string values
This one is actually a bit of a softball with JavaScript however it compounds issue 2 so lets state the standard solution.
```javascript
// Storing your data
const setData = (key, data) => {
  const DATA = JSON.stringify(data)
  localStorage.setItem(key, DATA)
}

// Retrieving your data
const getData = key => {
  const DATA = localStorage.getItem(key)
  return JSON.parse(DATA) 
}

// Deleting your data
const removeData = key => localStorage.removeItem(key)
```
This seems to be the common pattern that I see. It works but its not something that I love. Before I move on I am going to slightly refactor my code to give us a little cleaner API to work with.
```javascript
/**
 * @class Storable
 * @param {id:string|number} - Storage key
 */
class Storable {
  id

  constructor(id) {
    if(typeof id === 'undefined')
      throw new Error('Storables require a key to interact')
    this.id = id
  }

  get exists() {
    return !!localStorage.getItem(this.id)
  }

  get data() {
    const DATA = localStorage.getItem(this.id)
    return JSON.parse(DATA)
  }

  set data(data) {
    const DATA = JSON.stringify(data)
    localStorage.setItem(this.id, DATA)
  }

  remove() {
    localStorage.removeItem(this.id)
    return true
  }

}

/**
 * Usage example
 */
const person = new Storable('lucas')
person.data = {
  name: "Lucas",
  job:  "Engineer"
}

console.log(person.data) // { name: "Lucas", job: "Engineer" }
person.remove()
```
Udfa! That already feels better 😎. For anyone who is less familiar with what exactly we built, we can now create an object that allows us to manipulate our data without any real boilerplate or helper functions.
### Issue 2 - read / write actions
For this we should start with the understanding that these actions are going to still be synchronous. But how much does this effect the end user, how frequently do these operations need to occur, and what is the middle ground. 
I am a huge fan of [Svelte](https://svelte.dev/) and my approach to this actually stems from another gist that I wrote earlier called [storeWithEffect](https://gist.github.com/lpshanley/c987a23c7fe3136d26d42b11b2f68a63). If you aren't familiar with svelte or its stores I really recommend watching this talk [Rethinking Reactivity](https://www.youtube.com/watch?v=AdNJ3fydeao) by {% user richharris %}
My solution is tailored around svelte however the general idea would work with RxJS also. The idea is to create a reactive object that you can start working with. My idea is to essentially get an object "hot" into memory and then only cool it off when a user is done interacting with it. In this way yes it's synchronous however the action is performed only once and at a time when the users action is done.

By combining the idea of a reactive store, debouncing, and easy to use methods to interact with `localStorage` automatically.

{% gist https://gist.github.com/lpshanley/c987a23c7fe3136d26d42b11b2f68a63 %}
This gist contains a wrapper around the default svelte writable store, it accepts to extra bits of info however a debounce time and an effect.

```javascript
/**
 * Lets assume that we will continue to use
 * our Storable class we wrote above.
 */

const personStorable = new Storable('lucas')

const person = writableWithEffect(personStorable.data, {
  debounce: 400,
  effect(state) {
    personStorable.data = state
  }
})

/**
 * Basic usage
 */
person.set({ job: "Engineer" })
person.update(state => {
  return {
    ...state,
    name: "Lucas"
  }
})

person.subscribe(state => console.log(state))
```
This will now wait 400ms after the last update to the store and then automatically persist the change to `localStorage`.

## Final thoughts
In this way we get 100% of the benefits from reactive programming without the costs that would be imposed from all the read and writes.

I will be posting a second idea to this with another layer of abstraction that I am working through at the moment. I hope anyone who stuck it out enjoyed reading this and maybe learned something 😎!