Skip to content

Instantly share code, notes, and snippets.

@saulodias
Last active August 23, 2024 12:11
Show Gist options
  • Save saulodias/268ff05763ca54046b44b49c83b01034 to your computer and use it in GitHub Desktop.
Save saulodias/268ff05763ca54046b44b49c83b01034 to your computer and use it in GitHub Desktop.
A TypeScript module with useSessionStorage and useLocalStorage React hooks to save values in the browser sessionStorage or localStorage.
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 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment