Local Storage in JavaScript
In the browser, you can access local storage through the window.localStorage
property. The window
is assumed though, so you can omit it and simply write localStorage
.
Local storage gives access to 4 important functions:
setItem(key, value)
- assign a string value to a key in local storage.getItem(key)
- get the value assigned to a key.removeItem(key)
- removes the value by key.clear()
- removes all values in local storage.
Here's the usage example:
const value = { name: 'Vincas', surname: 'Stonys' };
// 1️⃣ Stores the value. Must convert value to string first
localStorage.setItem('userInfo', JSON.stringify(value));
// 2️⃣ Gets the stored value and convert it to original value type
JSON.parse(localStorage.getItem('userInfo'));
What is local storage?
Local storage is basically a simple key-value map in your browser. It has a simple synchronous API that's available in all modern browsers.
The local storage is separate for every origin (hostname and port): https://vistontea.com
and https://blog.vistontea.com
won't share local storage.
You can inspect values stored in local storage via developer tools. For instance, using CTRL+SHIFT+C
keyboard shortcut in Chrome (or CMD+SHIFT+C
on Mac), then opening the Application tab.
Local Storage in React
In React, you can use local storage directly using plain JavaScript.
A common use case for local storage is to persist local component state. In that case, it's useful to write a custom hook that synchronizes component state to local storage.
Here's a custom useStoredState
hook example 👇
import { SetStateAction, useState } from 'react';
function useStoredState<T>(key: string, defaultValue?: T | (() => T)) {
// 👇 Load stored state into regular react component state
const [state, setState] = useState<T>(() => {
const storedState = localStorage.getItem(key);
if (storedState) {
// 🚩 Data is stored as string so need to parse
return JSON.parse(storedState) as T;
}
// No stored state - load default value.
// It could be a function initializer or plain value.
return defaultValue instanceof Function ? defaultValue() : defaultValue;
});
// 👇 Keeps the exact same interface as setState - value or setter function.
const setValue = (value: SetStateAction<T>) => {
const valueToStore = value instanceof Function ? value(state) : value;
localStorage.setItem(key, JSON.stringify(valueToStore));
setState(valueToStore);
};
// as const tells TypeScript you want tuple type, not array.
return [state, setValue] as const;
}
- The hook keeps the same return type as
useState
- a tuple with the value and value setter. Its parameters are the local storage key and the default value or the initializer function. - It initializes the local component state by getting its value by
key
from local storage and parsing it in the state initializer function. - A custom state setter function wraps the native
setState
with additional logic to store the value in local storage usinglocalStorage.setItem
function.
Here's how you might use it:
import * as React from 'react';
import useStoredState from './useStoredState';
export default function App() {
// 👇 Will be stored in local storage, but used as regular state
const [items, setItems] = useStoredState('items', ['item1', 'item2']);
const handleAddClick = () => {
setItems((items) => [...items, `item${items.length + 1}`]);
};
return (
<div>
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<button onClick={handleAddClick}>Add</button>
</div>
);
}
You can see the full code example with demo here: https://stackblitz.com/edit/react-ts-x4nwoj?file=App.tsx
This is how it looks like in action 👇
Using Local Storage in TypeScript
You will notice that the localStorage
property doesn't have great typings. It's useful to create your own wrapper around local storage that handles automatic value serialization and parsing.
Here's an example 👇
const storage = {
set: (key: string, value: any) => {
localStorage.setItem(key, JSON.stringify(value));
},
get: <T>(key: string, defaultValue?: T): T => {
const value = localStorage.getItem(key);
return (value ? JSON.parse(value) : defaultValue) as T;
},
remove: (key: string) => {
localStorage.removeItem(key);
},
};
export default storage;
You could now use the storage
utility instead of localStorage
and provide the type for the value stored in local storage:
// No need to parse the value, it's handled by the wrapper
const value = storage.get<{ name: string, surname: string }>('userInfo');
Conclusion
Local storage is the easiest way to persist value in the browser, often eliminating the need for the backend server in the case of frontend-only apps.
In JavaScript, it's as simple as using functions exposed by localStorage
object. In React you may use the same API, however, it's helpful to be more declarative and create higher-level utilities. I've given an example of a custom hook that synchronizes component state to local storage.