enthusiastic-js / form-observer

A simple utility for reacting to events from a form's fields
MIT License
7 stars 0 forks source link

First Draft of `FormStorageObserver` #2

Closed ITenthusiasm closed 1 year ago

ITenthusiasm commented 1 year ago

Key Design Decision: Only Allow FormStorageObserver to Support localStorage

This implementation of FormStorageObserver is a divergence from what I originally had in mind for the class.

Originally, my hope was for the FormStorageObserver class to support all kinds of form data storage. By default, the class would use localStorage to store form data; but the user would be able to supply any custom interface that had the setItem, getItem, and removeItem methods -- just like localStorage. This way, they'd be able to store form data any way they wanted (for instance, in an in-memory object) -- not just with localStorage. Unfortunately, after trying to work with the idea, I concluded that it wasn't practical or worthwhile, and I abandoned the idea entirely.

When it comes to storing or retrieving form data, we need to know the kind of data being stored because this influences how the data is transformed during data storage/retrieval. That is, we need to know "Is this data for a checkbox?" ("Should I store this as a stringified boolean?"), "Is it data for a multi-select?" ("Should I store this as a stringified array?"), "Is it data for a regular text input?" ("Should I store this as a stringified string [sic]?"), etc. This information is necessary to make sure that the form fields are correctly updated whenever data is loaded from the "storage" into the actual fields. For instance, a checkbox will need its checked property/attribute updated with a valid boolean during loading, whereas a regular text input will need its value property/attribute udpated with a valid string.

Additionally, the setItem, getItem, and removeItem methods of the localStorage interface expect to be working with stringified data. But perhaps not everyone wants to work with stringified data. For instance, maybe they want the setItem method to accept a generic data type instead of just a string. (In other words, perhaps they want to store and retrieve data as a literal boolean instead of as a stringified boolean.)

This situation brings up a lot of questions if we want users to be able to store data in different ways. Do we want users to be able to supply "transformers" for data storage/retrieval? How do we enforce that the end users write "transformers" that take into account the types of data being stored (checkbox data, mutli-select data, text field data, etc.)? Do we want users to only be able to store stringified data (as in the case of localStorage), or do we want to support other types of data? Does the end user even want to be bothered with these concerns? How will our FormStorageObserver class handle these things in a clear and performant way without sacrificing the code's maintainability?

The questions go on and on. And although valid solutions exist, trying to handle these cases would ultimately make FormStorageObserver slower and more difficult to maintain. Moreoever, it's highly unlikely that a significant number of people are going to be desperate for a storage option besides localStorage. Most folks just want an easy way to throw form data into localStorage. That's it. It's sufficient to support that instead of diving into impractical use cases.

A big benefit of making a FormStorageObserver that only supports localStorage is that in some cases we can leverage local functions or static class methods that are only defined once. (This should save on memory per each instance of FormStorageObserver.) Another big benefit is that the code remains very simple since we get to define how the data transformation operates. (And from this information, we can make decisions that optimize performance when interacting with localStorage.) So ... it seems wiser for now to only support localStorage.

If users truly needed something more, we could add it. However, it would probably make more sense for the end user to create their own solution. More than likely, the end user will have a single preferred way of storing form data across their application. (Consistency is incredibly valuable.) Thus, if for any reason something other than localStorage was needed (again, highly unlikely), then the user could extend FormObserver to create something similar to FormStorageObserver -- but for their specific use case. The public code in the FormStorageObserver file should make it clear how to go about that. (This is another reason to keep FormStorageObserver clear and maintainable).

For now, we shouldn't overcomplicate things if localStorage is what will be used by most people for form data storage.

Additional Thoughts

Why Aren't You Supporting sessionStorage?

Because sessionStorage and localStorage behave the exact same way when it comes to their setItem, getItem, and removeItem methods, it would be possible for FormStorageObserver to allow the end developer to choose which kind of storage they used. However, I consider this impractical for 3 reasons:

First: The user experience is better with localStorage. If a user is working through a long/complex form and they are forced to close the browser for any reason (or perhaps they accidentally close their tab/browser), they will lose all of their progress if sessionStorage is used. Not so with localStorage. Thus, why bother with sessionStorage if localStorage provides a better user experience?

Second: We lose the benefit of static methods/functions if we support multiple storage options. When we choose to exclusively use localStorage, we can simplify things by creating local functions and static class methods that are only defined once. But if the end developer starts dictating what kind of storage is used, that information will need to be passed in as an argument to the constructor. This ultimately means that our methods will need to refer to something like this.#storage instead of window.localStorage, which in turn means that what could have been a static method will need to be changed into an instance method, which in turn means that we'll be recreating functions that effectively do the exact same thing for each instance of FormStorageObserver. Seems wasteful, doesn't it?

Third: If the life of the form data is a concern, the developer will need to take responsibility for it anyway. Although it would be a little odd, someone could argue that using sessionStorage makes life easier because the form data would automatically be cleared when the browser tab is closed. However, it doesn't make sense to leave a user's storage cluttered with obsolete data for any period of time. As soon as the form data becomes irrelevant, it should be removed. This benefits the users of our web apps, and it likely shields us developers from potential sources of unnecessary confusion. Whether localStorage or sessionStorage is used, this cleanup will have to be done; so we may as well keep things simple by keeping to one approach over the other (in light of the earlier points).