DEV Community

Cover image for Learn the Storage API by Building a Note Taking App
Andrew Healey
Andrew Healey

Posted on • Updated on • Originally published at healeycodes.com

Learn the Storage API by Building a Note Taking App

Let's learn about window.localStorage by building a tiny note-taking app with some simple HTML, CSS, and JavaScript. We'll code up a small UI and autosave the user's notes to their browser. My co-worker keeps all of his snippets on something just like this. I can't believe the mileage he gets out of 50-60 lines of code!

Most client-side storage interactions used to involve manipulating cookies but luckily we now have modern APIs. With cookies, the user sends everything stored with every request meaning worse performance for both parties.

Enter: localStorage. A key/value API that's available on the window object. Unlike sessionStorage which lasts for the during of the page session, localStorage has no expiration time.

All keys and values are converted to Strings. Let's add, update, and delete a key, and then try to read from an empty key. This is console input so the return value follows the command.

// Add key - or overwrite existing key
window.localStorage.setItem('foo', 'bar')
> undefined

// Read a value from a key
window.localStorage.getItem('foo')
> "bar"

// Delete key - deleting empty yields same return value
window.localStorage.removeItem('foo')
> undefined

// Read from a deleted or unknown key
window.localStorage.getItem('foo')
> null
Enter fullscreen mode Exit fullscreen mode

Other ways you might interact with localStorage involve clearing all keys and iterating through existing keys.

// Get the read-only length
window.localStorage.length
> 6

// Get the nth key
window.localStorage.key(0)
> "algoliasearch-client-js"

// Delete all keys
window.localStorage.clear()
> undefined

// Iterate through all keys
for (let i = 0; i < window.localStorage.length; i++) {
    console.log(window.localStorage.key(i));
}
Enter fullscreen mode Exit fullscreen mode

You can try some of these out in your developer console right here. DEV stores the following localStorage keys for me as I'm writing. Make sure not to delete any of these though! algoliasearch-client-js, config_body_class, current_user, https://dev.to/new, pusherTransportTLS.

The App

Here's what we'll be making. A small but practical note-taking app. Aside from spacing, it uses little styling (send a pull request). What's important is how the app communicates with the client's browser storage.

Hopefully, you're already thinking of the localStorage methods that we'll need. Let's use a ready-built HTML template that names the functions that we'll be using.

<h5>Tiny Note Taker</h5>
<div>
    <button onclick="newNote()">New</button>
        <select id="notes" onchange="changeNote()"></select>
        <button onclick="deleteNote()">Delete</button>
</div>
<textarea id="editor" rows="10" onkeyup="saveNote()"
    onkeydown="checkEmpty()" autofocus></textarea>
<script src="./index.js"></script>
Enter fullscreen mode Exit fullscreen mode

Take a second to think about how these functions might relate to the storage methods we saw just now, and how they will manipulate the DOM.


newNote() Prompt the user for a note name and create it.

changeNote() Swap to the new note when the selected element is changed.

deleteNote() Delete the currently selected note.

saveNote() Save the currently selected note as the user types.

checkEmpty() — This one is less obvious.

If a user arrives at the page and starts typing before creating a new note, the name would be "" an empty String. Instead, as they type but before the note is saved name the current note untitled. We simplify our logic here by separating it into two events. onkeydown checks for an empty note and fixes if necessary. This means that onkeyup can simply save the note without any additional checks. Thus, checkEmpty().

Reminder: Key events are almost always called in this order onkeydown -> onkeypress -> onkeyup and they all have their place!

Let's start with our base case. Saving one note as the user types. Since we've placed the script include at the bottom of the HTML, we can grab a reference to the two elements we need without waiting for any document load events.

const notes = document.querySelector('#notes');
const editor = document.querySelector('#editor');
Enter fullscreen mode Exit fullscreen mode

Great. Those references will last for the lifetime of this script as we'll never delete those elements. Next, we need to title the empty-named note as untitled if the user types without first hitting the New button.

/**
 * Check for empty note title.
 */
function checkEmpty() {
    if (notes.length === 0) {
        const untitled = document.createElement('option');
        untitled.innerText = 'untitled';
        notes.appendChild(untitled);
    }
}
Enter fullscreen mode Exit fullscreen mode

Here we create an option element with JavaScript and make it available in the select dropdown. We don't need to do any saving yet, that will happen a few milliseconds later when the user lifts their finger off the current key.

A few milliseconds later.

/**
 * Save editor text to storage under the current note.
 */
function saveNote() {
    window.localStorage.setItem(notes.value, editor.value);
}
Enter fullscreen mode Exit fullscreen mode

There's localStorage.setItem! We know what that does. It will save the note text as the value and the note title ("untitled") as the key. Now we have a problem to solve. If the user leaves and comes back, the select dropdown will be empty. Worse: they will overwrite their "untitled" note.

As our script runs, just after we grab references to those elements, let's populate the select dropdown with all the user's saved notes.

const notes = document.querySelector('#notes');
const editor = document.querySelector('#editor');

// Load user's saved notes
for (let i = 0; i < window.localStorage.length; i++) {
    const newNote = document.createElement('option');
    newNote.innerText = window.localStorage.key(i);
    notes.appendChild(newNote);
}
changeNote();
Enter fullscreen mode Exit fullscreen mode

Here we use the read-only length property of localStorage as well as its method key(). All saved notes are loaded into the select dropdown. We let the HTML operate as our state. This way we don't need to keep track of any of the notes in our JavaScript. We rely on simple functions to link up the DOM to the Storage API.

At the bottom of that last snippet, we saw changeNote() which loads the currently selected note into the textarea element a.k.a. the editor. This function is also called after a new note is created, which clears the editor. New notes are initialized with an empty String.

/**
 * Change editor text to the currently selected note.
 */
function changeNote() {
    editor.value = window.localStorage.getItem(notes.value);
}
Enter fullscreen mode Exit fullscreen mode

The Storage API abstracts away the heavy lifting for us.

User's need a way to name and create notes. newNote() does just that. It uses Element#insertBefore to place the new note at the top of the selectbox. This method is called on the parent element. var insertedNode = parentNode.insertBefore(newNode, referenceNode);. What's neat about this method is that it accepts a null value as the second argument. Meaning we can call insertBefore on empty parent elements!

/**
 * Ask the user to name their new note then create it.
 * Add this note to the select div then focus to it.
 */
function newNote() {
    const note = prompt('Name of note?');
    window.localStorage.setItem(note, '');

    const noteElem = document.createElement('option');
    noteElem.innerText = note;
    notes.insertBefore(noteElem, notes.firstChild);

    // Focus this note
    notes.value = note;
    changeNote();
}
Enter fullscreen mode Exit fullscreen mode

Our note taking app is nearly finished. The final function required is for deleting notes. When a user is finished with a note, they can select it and then hit the Delete button. The trickiest part about this function is finding the right note to delete in the select dropdown. One of the drawbacks to using the DOM as our state. We have to loop through every option, checking the value.

/**
 * Delete currently selected note
 */
function deleteNote() {
    const note = notes.value;
    window.localStorage.removeItem(note);
    editor.value = '';
    for (let i = 0; i < notes.length; i++) {
        const option = notes[i];
        if (option.value === note) {
            notes.removeChild(option);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

We've now got a fully functioning note taking app, infinitely scalable through a CDN. We used the powerful Storage API and learnt how to use the DOM as application state. Go us! 🙌

Check out the final application code here, and the live version here.


Join 150+ people signed up to my newsletter on programming and personal growth!

I tweet about tech @healeycodes.

Top comments (6)

Collapse
 
jimgregory profile image
Jim Gregory • Edited

Thanks, that was very useful.

Here's another way to delete the note that avoids looping through all the elements:

function deleteNote() {
    const note = notes.value;
    const options = document.querySelectorAll('options');
    window.localStorage.removeItem(note);
    editor.value = '';
    notes.removeChild(notes[notes.selectedIndex]);
}
Collapse
 
healeycodes profile image
Andrew Healey

This is really neat! I felt like there should have been a way to avoid that iteration. Thanks 👍

Collapse
 
galgreenfield profile image
Gal • Edited

Do modern, widely-used data-cleaning softwares/apps such as CCleaner and Clean Master recognize localStorage, and can they delete them?

I'm asking because if I were to have users who use these apps, if there's a chance that they'd delete their storage, and if there is a chance, I'd like to know exactly what kind of a warning message to display to them, like websites do with cookies.

Collapse
 
healeycodes profile image
Andrew Healey

Users can delete their localStorage for all sites by doing a full clear of their cache/browsing history. See here for a more complete answer. It therefore seems likely that either of those applications would also perform this 'cleaning'.

Perhaps you could allow your users to export and import their data as a way around this 👍

Collapse
 
vjnvisakh profile image
Visakh Vijayan

Nice and simple.

Collapse
 
rohovdmytro profile image
Rohov Dmytro • Edited

Tiny indeed! :)