protips

Logo

This site lists the protips that we shared with students during our courses

View the Project on GitHub appliedtechnology/protips

LocalStorage - the little database in your browser

(The full code will be listed at the end of this file)

Most applications that we write operate around data of different kinds; creating, viewing, sorting and generally managing data are among the most common things your users will do in the applications we create.

The data is most often stored through the back-end in a database, but quite frequently there’s a need to hold this data in the front-end parts of the application. At least for a while - before the users decide to save it permanently. This is called “the application state”.

It’s quite easy to create an application state in JavaScript because it’s just any kind of data as a global variable.

Let’s pretend that we want to store some information about your family’s shoe-sizes, to make a nice application to manage shoe-shopping.

const myFamily = [
  { name: 'Marcus', shoeSize: 46, birthYear: 1972 },
  { name: 'Elin', shoeSize: 36, birthYear: 1977 },
  { name: 'Arvid', shoeSize: 36, birthYear: 2010 },
  { name: 'Gustav', shoeSize: 32, birthYear: 2010 },
  { name: 'Albert', shoeSize: 35, birthYear: 2008 }
]

console.table(myFamily)

This works perfectly and we see a nice table printed. Console is awesome.

The only problem is that if we were to change this data and then reload the page - our changes are gone. Everyone will always have just my family data since it is hardcoded and the myFamily constant will be set to the people above on each reload of the page.

We want some way to store this locally. Some kind of … local storage.

Local storage

LocalStorage is a property on the window object and can hence be accessed by any application running in a browser. It gives us a fast way to store some state data for our application with very little hassle:

localStorage // or window.localStorage

Running this command in the browser console with show you the contents of it (Storage {length: 0})

You can also view it in the Chrome Developer tools under the Application-tab.

Using localStorage in code

LocalStorage is a key / value storage and hence you can store data under a key, like this localStorage.setItem('key', 'value').

Note that localStorage stores strings… So if we want to store something bit more complex, like a JavaScript object we need to convert this to JSON, using JSON.stringify() and JSON.parse().

Storing items with .setItem

Let’s create a simple UI to initialize an application state

<button id="init">Init state</button>

and then seed it with that same family…

const initButton = document.querySelector('#init')
initButton.addEventListener('click', () => {
  const initialState = {
    family: [
      { name: 'Marcus', shoeSize: 46, birthYear: 1972 },
      { name: 'Elin', shoeSize: 36, birthYear: 1977 },
      { name: 'Arvid', shoeSize: 36, birthYear: 2010 },
      { name: 'Gustav', shoeSize: 32, birthYear: 2010 },
      { name: 'Albert', shoeSize: 35, birthYear: 2008 }
    ]
  }

  localStorage.setItem('myAppState', JSON.stringify(initialState))
})

Notice that I created an object that represented my whole state, and family is one property on it.

Also, notice that I JSON.stringify and stored it under the myAppState - key.

If you now open the Application / Local Storage part of the Google Chrome console you can see the state being stored under the URL of the application.

Get data back out using .getItem

Very cool - it’s stored. But is it?

Let’s create another button to get the data back out again:

<button id="log">Print current state to console</button> <br>
const logButton = document.querySelector('#log')
logButton.addEventListener('click', () => {
  const state = JSON.parse(localStorage.getItem('myAppState'))
  console.log(state)
  console.table(state.family)
})

I now get the whole state out and print the entire state, as well as the .family property.

Now for the big reason for using localStorage. Close the browser and the server. Then restart it again and click the Print current state to console again.

What do you think will happen?

YES - the data is still there. Because it’s saved in our local storage and is persistent over sessions.

Updating state

Now I can manipulate the state as any normal JavaScript object. Let’s write a button that increments the shoe-size of all kids… It seems to happen often enough for me to want a button for that.

First, here’s a little function that updates all kid’s shoe sizes with one. I will not comment on it - just trust me:

const incrementKidsShoeSize = (family) => {
  const currentYear = new Date().getFullYear()
  const parents = family.filter(p => currentYear - p.birthYear > 18)
  const kidsWithBiggerFeet = family
    .filter(p => currentYear - p.birthYear <= 18)
    .map(p => ({
      shoeSize: p.shoeSize + 1,
      name: p.name,
      birthYear: p.birthYear
    })
    )
  return [...parents, ...kidsWithBiggerFeet]
}

Now we can create a button:

<button id="increment">Increment shoe-sizes</button> <br>

And then an event handler:

const incrButton = document.querySelector('#increment')
incrButton.addEventListener('click', () => {
  const state = JSON.parse(localStorage.getItem('myAppState'))

  state.family = incrementKidsShoeSize(state.family)

  localStorage.setItem('myAppState', JSON.stringify(state))
})

I’m pulling out the complete state, updating the family with the result of the incrementKidsShoeSize function. Then I put it back in the localStorage under the same key. In effect overwriting it.

This is the easiest way I know to handle updating the application state. It’s a bit crude but it works great. And it resembles a lot of React and Redux does it.

Clear state

One final thing: you might want to clear the state completely for which localStorage.clear() is the function to use.

Conclusion

LocalStorage is a very simple tool that helps us to persist in the application state, even between sessions. It is just a key/value storage and is very simple to use.

In this post, we have stored an initial state with an array of objects, pulled them back out of state, manipulated them and then put them back into the state. We have also learned how to clear the state.

The codez

Here is a complete listing of the code used in this blog post

Index.html

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Demo page for local storage</title>
</head>
<body>
  <h1>Nothing to see here</h1>
  <button id="clear">Clear local storage</button> <br>
  <button id="init">Init state</button> <br>
  <button id="log">Print current state to console</button> <br>
  <button id="increment">Increment shoe-sizes</button> <br>
  <script src="index.js"></script>
</body>
</html>

Index.js

const clearButton = document.querySelector('#clear')
clearButton.addEventListener('click', () => {
  localStorage.clear()
})

const logButton = document.querySelector('#log')
logButton.addEventListener('click', () => {
  const state = JSON.parse(localStorage.getItem('myAppState'))
  console.log(state)
  console.table(state.family)
})

const initButton = document.querySelector('#init')
initButton.addEventListener('click', () => {
  const initialState = {
    family: [
      { name: 'Marcus', shoeSize: 46, birthYear: 1972 },
      { name: 'Elin', shoeSize: 36, birthYear: 1977 },
      { name: 'Arvid', shoeSize: 36, birthYear: 2010 },
      { name: 'Gustav', shoeSize: 32, birthYear: 2010 },
      { name: 'Albert', shoeSize: 35, birthYear: 2008 }
    ]
  }

  localStorage.setItem('myAppState', JSON.stringify(initialState))
})

const incrButton = document.querySelector('#increment')
incrButton.addEventListener('click', () => {
  const state = JSON.parse(localStorage.getItem('myAppState'))

  state.family = incrementKidsShoeSize(state.family)

  localStorage.setItem('myAppState', JSON.stringify(state))
})

const incrementKidsShoeSize = (family) => {
  const currentYear = new Date().getFullYear()
  const parents = family.filter(p => currentYear - p.birthYear > 18)
  const kidsWithBiggerFeet = family
    .filter(p => currentYear - p.birthYear <= 18)
    .map(p => ({
      shoeSize: p.shoeSize + 1,
      name: p.name,
      birthYear: p.birthYear
    })
    )
  return [...parents, ...kidsWithBiggerFeet]
}