useLocalStorage
Custom hook that uses the localStorage API to persist state across page reloads.
Usage
import { useLocalStorage } from 'usehooks-ts' export default function Component() { const [value, setValue, removeValue] = useLocalStorage('test-key', 0) return ( <div> <p>Count: {value}</p> <button onClick={() => { setValue((x: number) => x + 1) }} > Increment </button> <button onClick={() => { setValue((x: number) => x - 1) }} > Decrement </button> <button onClick={() => { removeValue() }} > Reset </button> </div> ) } API
▸ useLocalStorage<T>(key, initialValue, options?): [T, Dispatch<SetStateAction<T>>, () => void]
Custom hook that uses the localStorage API to persist state across page reloads.
Type parameters
| Name | Description |
|---|---|
T | The type of the state to be stored in local storage. |
Parameters
| Name | Type | Description |
|---|---|---|
key | string | The key under which the value will be stored in local storage. |
initialValue | T | () => T | The initial value of the state or a function that returns the initial value. |
options? | UseLocalStorageOptions<T> | Options for customizing the behavior of serialization and deserialization (optional). |
Returns
[T, Dispatch<SetStateAction<T>>, () => void]
A tuple containing the stored value, a function to set the value and a function to remove the key from storage.
Type aliases
Ƭ UseLocalStorageOptions<T>: Object
Options for customizing the behavior of serialization and deserialization.
Type parameters
| Name | Description |
|---|---|
T | The type of the state to be stored in local storage. |
Type declaration
| Name | Type | Description |
|---|---|---|
deserializer? | (value: string) => T | A function to deserialize the stored value. |
initializeWithValue? | boolean | If true (default), the hook will initialize reading the local storage. In SSR, you should set it to false, returning the initial value initially. Default ts true |
serializer? | (value: T) => string | A function to serialize the value before storing it. |
Hook
import { useCallback, useEffect, useState } from 'react' import type { Dispatch, SetStateAction } from 'react' import { useEventCallback, useEventListener } from 'usehooks-ts' declare global { // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface WindowEventMap { 'local-storage': CustomEvent } } type UseLocalStorageOptions<T> = { serializer?: (value: T) => string deserializer?: (value: string) => T initializeWithValue?: boolean } const IS_SERVER = typeof window === 'undefined' export function useLocalStorage<T>( key: string, initialValue: T | (() => T), options: UseLocalStorageOptions<T> = {}, ): [T, Dispatch<SetStateAction<T>>, () => void] { const { initializeWithValue = true } = options const serializer = useCallback<(value: T) => string>( value => { if (options.serializer) { return options.serializer(value) } return JSON.stringify(value) }, [options], ) const deserializer = useCallback<(value: string) => T>( value => { if (options.deserializer) { return options.deserializer(value) } // Support 'undefined' as a value if (value === 'undefined') { return undefined as unknown as T } const defaultValue = initialValue instanceof Function ? initialValue() : initialValue let parsed: unknown try { parsed = JSON.parse(value) } catch (error) { console.error('Error parsing JSON:', error) return defaultValue // Return initialValue if parsing fails } return parsed as T }, [options, initialValue], ) // Get from local storage then // parse stored json or return initialValue const readValue = useCallback((): T => { const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue // Prevent build error "window is undefined" but keep working if (IS_SERVER) { return initialValueToUse } try { const raw = window.localStorage.getItem(key) return raw ? deserializer(raw) : initialValueToUse } catch (error) { console.warn(`Error reading localStorage key “${key}”:`, error) return initialValueToUse } }, [initialValue, key, deserializer]) const [storedValue, setStoredValue] = useState(() => { if (initializeWithValue) { return readValue() } return initialValue instanceof Function ? initialValue() : initialValue }) // Return a wrapped version of useState's setter function that ... // ... persists the new value to localStorage. const setValue: Dispatch<SetStateAction<T>> = useEventCallback(value => { // Prevent build error "window is undefined" but keeps working if (IS_SERVER) { console.warn( `Tried setting localStorage key “${key}” even though environment is not a client`, ) } try { // Allow value to be a function so we have the same API as useState const newValue = value instanceof Function ? value(readValue()) : value // Save to local storage window.localStorage.setItem(key, serializer(newValue)) // Save state setStoredValue(newValue) // We dispatch a custom event so every similar useLocalStorage hook is notified window.dispatchEvent(new StorageEvent('local-storage', { key })) } catch (error) { console.warn(`Error setting localStorage key “${key}”:`, error) } }) const removeValue = useEventCallback(() => { // Prevent build error "window is undefined" but keeps working if (IS_SERVER) { console.warn( `Tried removing localStorage key “${key}” even though environment is not a client`, ) } const defaultValue = initialValue instanceof Function ? initialValue() : initialValue // Remove the key from local storage window.localStorage.removeItem(key) // Save state with default value setStoredValue(defaultValue) // We dispatch a custom event so every similar useLocalStorage hook is notified window.dispatchEvent(new StorageEvent('local-storage', { key })) }) useEffect(() => { setStoredValue(readValue()) // eslint-disable-next-line react-hooks/exhaustive-deps }, [key]) const handleStorageChange = useCallback( (event: StorageEvent | CustomEvent) => { if ((event as StorageEvent).key && (event as StorageEvent).key !== key) { return } setStoredValue(readValue()) }, [key, readValue], ) // this only works for other documents, not the current one useEventListener('storage', handleStorageChange) // this is a custom event, triggered in writeValueToLocalStorage // See: useLocalStorage() useEventListener('local-storage', handleStorageChange) return [storedValue, setValue, removeValue] }