useLocalStorage

React hook to persist state in localStorage with cross-tab synchronization, custom serializers, and reactive callbacks.

pnpm add react-callback-hooks

Demo

localStorage["demo:note"]

Usage

Shorthand — key + optional callback:

import { useLocalStorage } from 'react-callback-hooks'

const [theme, setTheme, removeTheme] = useLocalStorage<'light' | 'dark'>(
  'app:theme',
  (value) => console.log('changed to', value)
)

Full props object:

const [theme, setTheme, removeTheme] = useLocalStorage<'light' | 'dark'>({
  key: 'app:theme',
  defaultValue: 'light',
  onChange: (value) => console.log('changed to', value),
  onRemove: () => console.log('key removed')
})

Parameters

Shorthand form

ParameterTypeDescription
keystringThe localStorage key.
onChange(value: T | null) => voidOptional. Called on every value change, including cross-tab syncs.

Object form

PropertyTypeDefaultDescription
keystringThe localStorage key.
defaultValueTnullValue returned when the key does not exist.
serializerSerializer<T>JSON.parse / JSON.stringifyCustom serializer for non-JSON values.
onChange(value: T | null) => voidCalled on every value change, including cross-tab syncs.
onRemove() => voidCalled when the key is removed via remove() or localStorage.clear().

Return Values

Returns a tuple [value, set, remove]:

IndexTypeDescription
valueT | nullCurrent value. null when the key is absent and no defaultValue is set.
set(value: T) => voidWrites to localStorage, updates state, fires onChange.
remove() => voidDeletes the key, resets to defaultValue, fires onRemove and onChange(null).

Custom Serializer

const [count, setCount] = useLocalStorage<number>({
  key: 'app:count',
  defaultValue: 0,
  serializer: {
    read: (raw) => Number(raw),
    write: (val) => String(val)
  }
})

Cross-tab Sync

onChange fires in every tab that shares the same key. No extra setup needed — the hook listens to the native window storage event internally.