DEV Community

Saulo Dias
Saulo Dias

Posted on • Edited on

10 2

React hooks: useSessionStorage and useLocalStorage

I needed a simple way to use the sessionStorage and localStorage with React, and one of the requirements was to use TypeScript. The idea from Robin Wieruch's original article is pretty simple. It encapsulates the browser sessionStorage or localStorage with the useState hook. The hooks actually return a stateful value, and a function to update it. All I had to do was to put everything together into a TypeScript module.

You can also customize the STORAGE_KEYS_PREFIX, which can be useful to avoid conflicts between storage keys from different applications you might be developing.

import { useState, useEffect } from 'react'
// Check if the code is running in the browser (Next.js support)
const isClient = typeof window !== 'undefined'
type StorageType = 'localStorage' | 'sessionStorage'
/**
* A prefix to identify session and local storage keys saved using
* the storage hooks in this application.
*/
export const STORAGE_KEYS_PREFIX = 'my-app_'
/**
* Interface for a JSON converter which provides methods to serialize
* and deserialize values to and from JSON strings.
*/
export interface JsonConverter<AppType, ParsedType> {
/**
* Serializes a value to a JSON-compatible object.
* @param value The value to be serialized.
* @returns The JSON-compatible object representation of the value.
*/
toJson: (value: AppType) => ParsedType
/**
* Deserializes a JSON-compatible object to a value.
* @param storedValue The JSON-compatible object to be deserialized.
* @returns The deserialized value.
*/
fromJson: (storedValue: ParsedType) => AppType
}
const storageFactory =
(storageType: StorageType, keyPrefix: string) =>
<AppType, ParsedType = AppType>(
storageKey: string,
fallbackState: AppType,
converter?: JsonConverter<AppType, ParsedType>
): [AppType, React.Dispatch<React.SetStateAction<AppType>>] => {
const storage: Storage | null = isClient ? window[storageType] : null
if (!storageKey) throw new Error(`"storageKey" must be a nonempty string, but "${storageKey}" was passed.`)
const [value, setValue] = useState<AppType>(() => {
if (!storage) return fallbackState
const storedString = storage.getItem(keyPrefix + storageKey)
if (storedString && converter) {
const parsedValue = JSON.parse(storedString)
return converter.fromJson(parsedValue)
} else if (storedString) {
return JSON.parse(storedString) // If no converter provided, return the parsed JSON directly
} else {
return fallbackState
}
})
useEffect(() => {
if (!storage) return
if (converter) {
const jsonValue = converter.toJson(value)
storage.setItem(keyPrefix + storageKey, JSON.stringify(jsonValue))
} else {
storage.setItem(keyPrefix + storageKey, JSON.stringify(value))
}
}, [value, storageKey, converter])
return [value, setValue]
}
/**
* Saves data in local storage.
* @param storageKey A string to identify the value being cached.
* @param fallbackState The default value when no value has been stored yet.
* @returns A stateful value, and a function to update it.
* @example
* const [collapsed, setCollapsed] = useLocalStorage('isSidebarCollapsed', false);
*/
const useLocalStorage = storageFactory('localStorage', STORAGE_KEYS_PREFIX)
/**
* Saves data in session storage.
* @param storageKey A string to identify the value being cached.
* @param fallbackState The default value when no value has been stored yet.
* @returns A stateful value, and a function to update it.
* @example
* const [collapsed, setCollapsed] = useSessionStorage('isSidebarCollapsed', false);
*/
const useSessionStorage = storageFactory('sessionStorage', STORAGE_KEYS_PREFIX)
export { useLocalStorage, useSessionStorage }
view raw storage.ts hosted with ❤ by GitHub

If you like this post, you'll also like:

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (3)

Collapse
 
link2twenty profile image
Andrew Bone

This is a cool approach for keeping data synced with storage. I faced a similar problem a while ago and came up with a not too dissimilar solution though it's more of a wrapper for storage rather than a direct line in and out of it.

Collapse
 
saulodias profile image
Saulo Dias • Edited
Comment hidden by post author
Collapse
 
andrewbaisden profile image
Andrew Baisden

Great article.

Some comments have been hidden by the post's author - find out more

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more