Saving forms
Tags: forms, localstorage, javascript, ux
Published: 2020-10-19 11:04:11

---
title: Saving forms
published: true
date: 2020-10-14 11:39:26 UTC
tags: forms,localstorage,javascript,ux
canonical_url: https://adactio.com/journal/17516
---

I added a long-overdue enhancement to [The Session](https://thesession.org) recently. Here’s the scenario…

You’re on a web page with a comment form. You type your well-considered thoughts into a `textarea` field. But then something happens. Maybe you accidentally navigate away from the page or maybe your network connection goes down right when you try to submit the form.

This is a textbook case for storing data locally on the user’s device …at least until it has safely been transmitted to the server. So that’s what I set about doing.

My first decision was choosing how to store the data locally. There are multiple APIs available: [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage), [`IndexedDB`](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). It was clear that `sessionStorage` wasn’t right for this particular use case: I needed the data to be saved across browser sessions. So it was down to `IndexedDB` or `localStorage`. `IndexedDB` is the more versatile and powerful—because it’s asynchronous—but `localStorage` is nice and straightforward so I decided on that. I’m not sure if that was the right decision though.

Alright, so I’m going to store the contents of a form in `localStorage`. It accepts key/value pairs. I’ll make the key the current URL. The value will be the contents of that `textarea`. I can store other form fields too. Even though `localStorage` technically only stores one value, that value can be a JSON object so in reality you can store multiple values with one key (just remember to parse the JSON when you retrieve it).

Now I know **what** I’m going to store (the `textarea` contents) and **how** I’m going to store it (`localStorage`). The next question is **when** should I do it?

I could play it safe and store the comment whenever the user presses a key within the `textarea`. But that seems like overkill. It would be more efficient to only save when the user leaves the current page for any reason.

Alright then, I’ll use the [`unload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/unload_event) event. No! Bad Jeremy! If I use that then the browser can’t reliably add the current page to the cache it uses for faster back-forwards navigations. [The page life cycle is complicated](https://developers.google.com/web/updates/2018/07/page-lifecycle-api).

So [`beforeunload`](https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event) then? Well, maybe. But modern browsers also support a [`pagehide`](https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event) event that looks like a better option.

In either case, just adding a listener for the event could screw up the caching of the page for back-forwards navigations. I should only listen for the event if I know that I need to store the contents of the `textarea`. And in order to know if the user has interacted with the `textarea`, I’m back to listening for key presses again.

But wait a minute! I don’t have to listen for _every_ key press. If the user has typed _anything_, that’s enough for me. I only need to listen for the _first_ key press in the `textarea`.

Handily, [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) accepts an object of options. One of those options is called “`once`”. If I set that to `true`, then the event listener is only fired once.

So I set up a cascade of event listeners. If the user types anything into the `textarea`, that fires an event listener (just once) that then adds the event listener for when the page is unloaded—and that’s when the `textarea` contents are put into `localStorage`.

[I’ve abstracted my code into a gist](https://gist.github.com/adactio/be3fb1e7c15a47a90f87c7df11158e2e). Here’s what it does:

1. Cut the mustard. If this browser doesn’t support `localStorage`, bail out.
2. Set the `localStorage` key to be the current URL.
3. If there’s already an entry for the current URL, update the `textarea` with the value in `localStorage`.
4. Write a function to store the contents of the `textarea` in `localStorage` but don’t call the function yet.
5. The first time that a key is pressed inside the `textarea`, start listening for the page being unloaded.
6. When the page is being unloaded, invoke that function that stores the contents of the `textarea` in `localStorage`.
7. When the form is submitted, remove the entry in `localStorage` for the current URL.

That last step isn’t something I’m doing on [The Session](https://thesession.org). Instead I’m relying on getting something back from the server to indicate that the form was successfully submitted. If you can do something like that, I’d recommend that instead of listening to the form submission event. After all, something could still go wrong between the form being submitted and the data being received by the server.

Still, [this bit of code](https://gist.github.com/adactio/be3fb1e7c15a47a90f87c7df11158e2e) is better than nothing. Remember, it’s intended as an enhancement. You should be able to drop it into any project and improve the user experience a little bit. Ideally, no one will ever notice it’s there—it’s the kind of enhancement that only kicks in when something goes wrong. A little smidgen of [resilient web design](https://resilientwebdesign.com/). A defensive enhancement.