Intro
How to work with local and session storage efficiently and in a scalable way. Let's create some facades to unify the maintenance of these!
Prelude
Local storage and session storage are the most commonly used mechanisms for storing data in web applications. I skip cookies 🍪 because I'm allergic to gluten.
Today we'll create a mechanism that will unify the work of these impostors, ensuring type safety and making reading and much manipulation simpler!
Why do we need a wrapper for local/session storage?
Analyzing the local/session storage API, you can see that their methods are identical - they have the same signature.
Loading
What catches the eye at first glance?
- The key is 'string'
- The received value is of type 'string'
- The APIs are identical
- No encapsulation
It's easy to make a typo...
Loading
In addition, we can by mistake run this code on the server side, which has no chance of working - both APIs are only available in the browser environment.
Loading
The error message may not necessarily guide you to the cause of the error. This is a problem, especially for beginners.
What about reading and writing?
We must do repetitive logic every time - parsing when reading and writing.
Loading
It's time for one last treat! What about mocking when testing? Well, we are facing the same duplication of logic and lack of strong typing.
Loading
As you can see there is a lot of it, and I still left out such things as:
- What if we need an additional method?
- No encapsulation
- Singleton on the entire application - easy to mess up other functionality
- We can create a collision - overwrite a value set in another functionality
We need to get it right!
A little inspiration to get started
Do you remember the Axios library? You probably do! In it, we create an instance of an object that, based on the passed configuration, returns you a specific, consistent and convenient to use API. We want to achieve the same result.
At Axios, we do just that:
Loading
Our small library will do just that:
Loading
Here are the methods calls:
Loading
Take a look at the gif below and see what the target idea is:
Loading
TypeScript will look out for us accordingly
Let's model local/session storage and write tests
To begin with, let's create a string literal type in which we define the supported data sources and let's add a storage function skeleton.
Loading
Now tests for an implementation that is not ready yet. The tests will be red until we write code to make them green - we will use TDD.
If you want to understand what test-driven development is and want to explore the secrets of testing, then I invite you to this article and the following course.
Loading
Loading
Loading
Loading
Well, and at the very end, a test that verifies the operation of several methods called after each other - as it will be done in real life.
Loading
Time for implementation - we make our tests green
Well, we have the models, the tests and we know how our API will look/work - now it's time for implementation which is what FE devs like us love the most.
We will start with a function that will throw an exception with an appropriate message if local/session storage is not defined in a global object - for example, we ran our function on the server side.
Loading
Next, we will add a method implementation for reading a single value: get. Note the casting that is necessary - the JSON.parse method does not allow you to pass a generic parameter.
Loading
It's time for getAll, which is designed to return all values set. Note the Record and its "strange" typing. We want the returned object to have the same keys as the passed interface and the corresponding values.
Loading
At this point, we have two tests that are green. This means that throwing exceptions and reading a single value and multiple values is working properly.
- throws an error if any storage is undefined ✔️
- allows to get all values ✔️
Now it's time to getKeys. Here there is no surprise. We simply return the keys, which we can access thanks to closure.
Loading
Why is it a function and not simply keys: keys. It must be a function that reads the "latest" values. If it was a variable - the value would be always the same - initially adjusted keys (empty array).
After this change, the next test becomes green.
- picks keys from local storage or session storage ✔️
Now it's time for the last four methods: remove, set, patch and clear.
Loading
In remove, we remove the value from the selected local/session storage, and then we get rid of the passed key from the array.
In set, we set the value and add the key. At the same time, we check if the key already exists. If it does, we do not add the same - why do we need duplicates?
In clear, we clean everything that has ever been set - not all local/session storage - this could negatively affect other functionality.
In the patch, we add as many values as we passed keys to the object. At the same time, we check if the passed value is by any chance undefined - this will cause an exception in JSON.parse when trying to read it, so we're skipping these values.
After all these code changes, our tests are as green as Shrek.
Complete example
If you want the final code you can get it in the following repository. Below you have the full solution displayed via code snippets.
Loading
Loading
Loading
Summary
Once again at ease with what we have achieved 🧂:
- We have developer-friendly exceptions
- Local/session storage management is modular
- We have type-safety and typo protection
- No need to store keys in variables
- We can easily add new methods and extend the solution
- The solution can be used to mock up values in tests as well
- Consistent API
- Less risk of collisions or overwriting values in other features
It is worth mentioning that introducing an abstraction such as so does not always make sense. In this case, it is useful, but it is worth conducting a comparison after the work is completed what is the profit and the result. Maybe it was not needed?
In my opinion, it is not essential, but it makes life easier.
Comments
Add your honest opinion about this article and help us improve the content.