Local Storage

Learning outcomes:

  1. What is localStorage
  2. Storing and retrieving data
  3. The length property and key() method
  4. Removing some or all data
  5. The storage event

Introduction

As we discussed in the previous Storage Introduction chapter, the Storage API has replaced cookies in storing data that is used solely for client-side purposes.

We also stated that the API has two mechanisms for storing data - one that holds onto data persistently and one that holds onto data for a browser session.

In this chapter we shall start with the former i.e the localStorage object. We'll see all the basics of storing data, including how to set key-value pairs, update exisiting data in storage, delete some or all of the data and many more such utilities typical for storage interfaces.

So why we are wasting time here... Let's enroll!

What is localStorage?

Essentially localStorage is a property on the global window object.

It's not an API on its own - rather it's based on the Storage API we discussed in the previous chapter.

It allows one to store data persistently, without any expiration time.

Combining all these details:

localStorage is a Storage object that allows one to store data persistently.

It's a property on window, whose value is an object; an object that is an instance of the Storage interface and that provides features to store data permanently on the client-side.

And this is the most eleborate it can get!

The Storage API stores data as key-value pairs.

The key, which is a string, maps to a given value, which is once again a string.

Since localStorage and sessionStorage are instances of Storage, whatever applies to Storage obviously applies to both of them.

Now all the experienced developers here will think: 'Isn't this just how objects work in JavaScript?'

And the answer is yes!

Storage uses the same theory that's used behind JavaScript objects i.e keys map to given values; but with a few exceptions.

Unlike objects, values in the Storage interface can only be strings. This is because, otherwise storing arrays or objects would require slightly more processing, and of course more memory.

Imagine how would a browser store an object as an object (not as a string) which has multiple properties, some of which point to objects themselves.

A browser can't store an object as an object - it has to serialize it i.e convert it into a string.

Likewise as a standard type, the Storage API treats all keys and values as strings.

Even if the browser allowed values to be pure objects, it would nonetheless have to perform the serialization process itself. Moreover in cases where objects are badly nested with subobjects (like the object o.foo.bar.baz), which isn't a surprise in JavaScript, the serializer will have to consider each subobject and convert that into a string as well.. It's just a lot of work!

So as a standard, Storage allows keys and values to only be strings.

If a key or value is not a string, then it's automatically coerced to one. We'll see all these type coercions taking place in real examples below.

Storing and retrieving data

Given that we now know what localStorage exactly is, we can move over to see how to store data with it.

Well working with it is super easy!

There are two ways to add data to localStorage:

  1. Use the old object-property syntax on the localStorage object, either in the dot notation (o.x) or in the bracket notation (o["x"]).
  2. Use methods defined by the Storage interface to add data. This way is recommended for reasons which'll be discussed in the next section.

Anyways, let's consider an example.

Suppose you want to store the following variable permanently, using localStorage:

var x = 10;

The way you can do so is shown below, first using the object-property syntax:

localStorage.x = 10; // dot notation

/* same as
localStorage["x"] = 10; // bracket notation
*/

This statement, in the dot notation, defines a property x on the localStorage object with a value of 10.

This value will be stored by the browser, together with the key x in local storage. The key will map to the value "10".

In the code above, we're assigning a number to localStorage.x but what gets stored is "10" - the number 10 stringified. This is because the number 10 is converted into a string before being stored.

Now let's access these values using the same object property syntax:

console.log(localStorage.x); // "10"

As you would agree, this notation definitely feels more natural to work with, but the thing is that it has some pitfalls to it, and is generally not recommended. We'll shortly see why?

The second way to accomplish all this logic is by using two methods on the localStorage object - setItem() and getItem().

It's time for you to guess a deal!

Make a logical guess on how many arguments does the method setItem() require and in which order.

  • 1 argument; key
  • 1 argument; value
  • 2 arguments; key and then value
  • 2 argument; value and then key

The method setItem(key, value) takes a key and a value and puts the key-value pair in storage.

Similarly, the method getItem(key) takes a key and returns its corresponding value, or else the value null if no such key exists.

Consider the following code:

localStorage.setItem("x", 10);

This sets a key on the localStorage object that maps to the value "10".

Once again, here we've specified a numeric value to be stored - regardless it doesn't throw an error. This is because the value is automatically converted into a string, just like in the previous object-property syntax.

To retrieve this value we pass the key "x" to the method getItem():

localStorage.getItem("x"); // "10"

And so in this way we can store data in local storage and then later on retrieve it for further use.

Just specify a key and/or a value and you're all set to go!

What will the following code log in the console?

var o = {x: 10};
localStorage.setItem("o", o);

console.log(localStorage.getItem("o"));
  • The object {x: 10}
  • The string "{x: 10}"
  • The string "[object Object]"

The length property

As with Array, NodeList, HTMLCollection and arguments objects, the length property on localStorage holds the number of keys on the object.

When localStorage has no data in it, the value of length is effectively equal to 0.

Consider the following code where we first create a key-value pair on localStorage and then log its length:

localStorage.a = "Hello World!";
console.log(localStorage.length); // 1
Here we're assuming that before the execution of this code, localStorage has no data in it!

Similarly, suppose that before executing the code below, localStorage has 2 key-value pairs already stored in it - one is the key a and the other is the key b.

Now when we execute the code below, obviously you can predict yourself what will length be equal to.

localStorage.c = "foo";
localStorage.d = "bar";

console.log(localStorage.length); // 4

It'll be equal to 4, since now we've added two further keys in localStorage.

Removing data

There may come certain occasions where one would want to remove a given peice of data fro localStorage.

How we can do this is once again split into two categories - the object-property syntax and a method of the Storage API.

First let's see how to use the former.

Recall, how to delete a property from a given object.. We use the delete keyword followed by the usual property-access expression.

This is exactly how we delete a property from localStorage, in the object-property syntax.

In the code below we remove the same key "x" that we created above (holding the value "10") using dot notation:

delete localStorage.x;

This is effectively the same as:

delete localStorage["x"];

The delete operator returns true when it is successful in removing a property from a given object. In this case, it will surely evaluate to true.

One should never ever rely on the return value of the delete keyword because it isn't consistent at all. For example, see the following code:
delete localStorage.noSuchProperty; // true
There isn't any such property on localStorage but nonetheless delete returns true.

Moving on, the methodical way to remove data from storage is to use removeItem().

The method removeItem(key) takes in a key and removes its corresponding data from storage.

Following is an illustration:

localStorage.removeItem("x");

This code removes the data corresponding to the key "x", along with the key itself (obviously), from storage.

Note that unlike the delete keyword, removeItem() does NOT return any Boolean to indicate whether it was successful or not. Rather it always returns undefined.

If you think for a moment, there isn't any reason to get a Boolean value after removing data from storage. There isn't any error that can happen while removing data and so, likewise, there's no point of returning a Boolean. Be thoughtful!

Clearing everything

Besides deleting some data from storage, another common concern of applications is to delete all data from storage.

Fortunately there is only one straightforward way to accomplish this and that is using the method clear().

Simply call the method clear() on a Storage object and it'll remove all its data - no arguments are required.

Take a look at the code below:

localStorage.a = 10;
localStorage.b = 10;

console.log(localStorage.length); // 2

// clear away everything
localStorage.clear();

console.log(localStorage.length); // 0

First we create two key-value pairs on localStorage and then log length, which, as expected, returns 2.

After this we call clear() to remove all data from localStorage, and then finally log length again. This time it returns 0 which clearly confirms the job of clear() - remove everything.

Now you wouldn't have realised the fact that in the discussion above, you've already witnessed one pitfall of using the object-property syntax to work with the Storage API.

That is, it isn't possible to remove everything from storage using the property syntax - there isn't any single statement which can do this!

Or better to say, it'll be a painful task to accomplish this manually using a loop, the delete keyword, the length property and the key() method (which we'll see below); yet with absolutely no guarantees of cross-browser support!

To boil it down, using methods is the right way to take!

Retrieving keys

In addition to all the useful methods we've seen above, the Storage interface provides another method key() to allow developers to obtain an nth key from storage.

Just pass in a numeric index to key() and it will throw out the key sitting at that very index.

Remember that this does not at all mean that localStorage stores ket-value pairs in an array where elements can be accessed via an index.

The method key() is given just for convenience - to be able to retrieve a given key from a Storage object. That's it!

How browsers implement this method, and in which order do they store keys is a matter that varies from browser-to-browser; and thereby it's recommended to never solely rely on the order in which keys are stored!

Anyways, a typical use case of key() can be to get all the keys from a given storage object, iteratively, in a for loop, handled by the length property.

Shown below is an example:

localStorage.a = 10;
localStorage.b = 50;
                                
for (var i = 0, len = localStorage.length; i < len; i++) {
    console.log(localStorage.key(i));
}

This code will merely log all the keys currently stored in the localStorage object.

b
a
The order in which keys are stored in localStorage is unpredicatable, at least for those who aren't browser developers. See how "b" is logged before "a" in the example above.

Therefore, it is recommended to never rely on the order of keys of localStorage!

However, note that this isn't the only way to retrieve all the keys from localStorage. Another way which cuts down this loop boilerplate is the method Object.getOwnPropertyNames().

Give the method an object, and it'll return an array of all the keys of the object.

Following we give this method the localStorage object and likewise get an array returned holding all its keys:

localStorage.a = 10;
localStorage.b = 50;
                                
var keys = Object.getOwnPropertyNames(localStorage);

console.log(keys); // ["b", "a"]
The order of keys in the returned array here will be the same as in the example above.

The storage event

When two windows pointing to the same local storage location are opened simultaneously and a storage query happens in the meanwhile in one of them, a storage event is dispatched.

Except for the window where the actual query takes place, all other open windows (pointing to the same storage location) receive the storage event.

To handle this event we can either use the onstorage handler on window or the addEventListener() method, passing it "storage" as the first argument.

The storage event is an instance of the StorageEvent interface which exposes the following properties:

  1. oldValue - holds the old value of a given key that has assigned some other value.
  2. newValue - holds the new value of a given key that has been assigned some other value
  3. key - holds the key that has received a change.
  4. storageArea - points to the localStorage object, in this case.
  5. url - holds the url of the page where the storage query took place.

Below we illustrate how the storage event works on a basic level. It's advised that you follow the given instructions carefully in order to understand everything more clearly.

We'll start by creating an HTML file with the following JavaScript code to store some data in our localStorage object:

localStorage.x = 10;

Go on and visit the following link to execute this right now.

Live Example

After this, we'll create another HTML page with the following JavaScript code, to handle the storage event:

window.onstorage = function(e) {
    console.log(e);
}
The storage event only fires on the window element - no other object!

With this in place, it's now time to open both these pages simultaneously in our browser. Assuming you've opened the link above, just open the link below as well, and you are good to go.

Live Example

And the game begins... Woah! Yayyy!

Open the tab for first HTML page where the localStorage.x = 10 statement is written. Go to the console and change the value of the key x to some value other than 10. (You gotta change it!)

Once you do this, go to the second window (where the storage event is being handled) and open the console. You'll witness a StorageEvent object been logged.

For those of who don't have access to the console, you can still witness all this action in the following two links.

In the first link you can change data on localStorage by pressing a button whereas in the second link you can witness the change, as it handles the storage event.

Changing localStorage data

Witnessing the change

It's important that the second link here is opened while you're changing data in localStorage in any other window. This is because if, otherwise, it's not opened then the storage event would be lost!

Now if you're unable to perform and understand any of the steps above, here's the deal!

The idea is that we update data on a localStorage object in one window; by either adding, modifying or deleting data; and then hear for the storage event in another window.

The same window where the storage query happens never gets the storage event - we ought to open two tabs to witness all this action!