0

I have an app that writes data to and reads data from the localStorage.

Basically I have an array of objects that I am changing and reading many times in the code, and all of these changes I want to keep in sync with the localStorage.

Now I could create a function that syncs it every time I need it in the code, but it just sounds a bit ugly and unoptimized to me. Like this

arrayOfObjects.push(newItem);
localStorage.setItem('objects', arrayOfObjects);

and then

arrayOfObjects = localStorage.getItem('objects');
length = arrayOfObjects.length

This would be really bad, because I have to read and write to it hundreds of times in the code, terrible solution.

Now I could also encapsulate it into a function so that all writes and reads and syncs got through it, but still, it will lack many functionalities because I will still need to deal with arrayOfObjects as an array type for multiple array operations like .push and .length.

Any ideas on how I could make this array sync with the localStorage with the fewest possible lines of code? Is there some way I could just write/read to an array type directly in localStorage?

sigmaxf
  • 7,998
  • 15
  • 65
  • 125
  • `but it just sounds a bit ugly and unoptimized to me` are you sure that's not premature optimisation? I would personally say make a `sync` function that does the job and see if it affects performance. If it does, then you can optimise it but once the interface is there, all you would need is to change `sync` and nothing else. You could, for example [debounce](https://davidwalsh.name/function-debounce) the function and that might be all you need. Or perhaps something different. Hard to say without knowing the concrete performance profile of your use case. – VLAZ Apr 11 '18 at 19:17
  • Sorry I didn't mean optimized in terms of performance, but in overall code readability and maintenance. – sigmaxf Apr 11 '18 at 19:34
  • In that case, you could possibly do the same - make all "changes" to the array via a call to a function, so you never interact with the array directly - that means that you can add whatever logic you want in there, including persisting the array to locastorage. Alternatively, you can set up the array to be an observable and attach an observer that syncs to local storage on any change. – VLAZ Apr 11 '18 at 20:25

1 Answers1

1

You can create your own Class which extends the native Array, the only difference being that it also saves it in LocalStorage when an operation is performed.

When you want to restore this special Array you would just call it's .restore() method which basically restores it back from LocalStorage.

Here's an example:

class LocalArray extends Array {
  constructor(name, ...items) {
    super(...items)
    this._name = name
  }

  push(item) {
    Array.prototype.push.call(this, item)

    this._save()
  }

  pop() {
    const popped = Array.prototype.pop.call(this)

    this._save()

    return popped
  }

  // rest of operations, i.e splice, reduce, etc..

  restore() {
    const data = window.localStorage.getItem(this._name)
    const items = data ? JSON.parse(data) : []

    items.forEach(item => {
      Array.prototype.push.call(this, item)
    })
  }

  _save() {
    window.localStorage.setItem(this._name, JSON.stringify(this))
  }
}

And this is how it's used:

const users = new LocalArray('users')
users.push({ name: 'John Doe' })
users.push({ name: 'Mary Jane' })
users.pop()

As you can imagine, LocalStorage would now contain an item 'users': [{"name":"John Doe"}].

.. and to restore:

const users = new LocalArray('users')
users.restore()

console.log(users) // logs [{ name: 'John Doe' }]

Keep in mind that there are significant performance implications here. Every time you .push() for example, you would be triggering a _save() on an ever-growing Array.

You can ameliorate that by saving deltas (changes) to your Array in LocalStorage, instead of the whole object, but at that point things start getting complicated.

Use wisely.

nicholaswmin
  • 21,686
  • 15
  • 91
  • 167
  • 1
    This might be one of the cases where a `Proxy` would be more appropriate. Subclassing doesn't really make sense here. – Bergi Apr 11 '18 at 21:16
  • @Bergi "Doesn't really make sense" is a rather strong and innapropriate statement. Proxies might be another alternative but this is still a viable solution. Although it's your prerogative I'd much prefer if you would write up answers describing your alternative solutions instead of leaving half-thought out comments on answers you don't find perfect. – nicholaswmin Apr 11 '18 at 21:29
  • I like your solution, but I'm also interested in learning how to do it using proxy, ideas? – sigmaxf Apr 11 '18 at 23:59
  • 1
    If anyone looks for a solution using proxy in the future: https://stackoverflow.com/questions/35610242/detecting-changes-in-a-javascript-array-using-the-proxy-object – sigmaxf Apr 12 '18 at 00:02
  • @NicholasKyriakides Subclassing is not viable if you want to detect things like `const users = new LocalArray('users'); users[0] = {name: 'John Doe'}; users[1] = {name: 'Mary Jane'};` and many others. – Bergi Apr 12 '18 at 08:27
  • @Bergi Then next time write this as a caveat in your comments and be a tad more constructive. The OP mentioned `push` in his code and my answer addresses it. Also you can use something like `.set(index, element)` method to do what you mention but this is besides my point. – nicholaswmin Apr 12 '18 at 08:31
  • @NicholasKyriakides Sorry, I thought mentioning proxies would've been constructive enough, and the difference would be obvious. It seems to have led the OP to a good resource at least, that's all I was aiming for :-) – Bergi Apr 12 '18 at 08:37
  • @Bergi Be more considerate with your comments next time. They deter the OP from seeing any value of the solution I propose and they deter me from writing up any answers if they are gonna be scrutinised to perfection. We are here to help and give different opinions. I'm referring to your `doesn't make sense` comment which more or less hints that this is a nonsense answer, which it is not. That's all. Pointing to `Proxies` was certainly constructive IMO. – nicholaswmin Apr 12 '18 at 08:40
  • @NicholasKyriakides OK, sorry for the language, that might have been inappropriate. You still got your upvote and accept, so no harm done :-) – Bergi Apr 12 '18 at 09:10
  • @Bergi Fortunately, I hardly care about up votes. Apology accepted. – nicholaswmin Apr 12 '18 at 09:11