useLocalStorage is a hook that binds React state to localStorage under a given key.
- Initializes the value from storage (if there is a valid string), otherwise from
initialValue. - Returns a tuple
[value, setValue]. setValueaccepts either a direct value or a functional updater(prev) => next.- Supports customizable
serializer/deserializer, as well as cross‑tab synchronization viawatch.
function useLocalStorage<ValueType = undefined>(
key: string,
initialValue?: undefined,
options?: UseLocalStorageOptions<ValueType>,
): UseLocalStorageReturn<ValueType | undefined>;
function useLocalStorage<ValueType>(
key: string,
initialValue: ValueType | (() => ValueType),
options?: UseLocalStorageOptions<ValueType>,
): UseLocalStorageReturn<ValueType>;-
Parameters
key— the key inlocalStorage.initialValue?— initial value or factory. Used if storage does not contain a valid string.options?— serialization and behavior settings:serializer?: (key, value) => string— converts a value to a string before writing.deserializer?: (key, raw) => Value | undefined— parses a string from storage. Returnundefinedto treat as “no value”.watch?: boolean— whether to update state when this key changes in other tabs.
-
Returns:
UseLocalStorageReturn<Value>— tuple[value, setValue].
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>(
'ui:theme',
'light',
);
// later, on toggle
setTheme('dark');const [count, setCount] = useLocalStorage<number>('app:count', 0);
const inc = () => setCount((prev) => (prev ?? 0) + 1);
const reset = () => setCount(0);const [token, setToken] = useLocalStorage<string | undefined>('auth:token');
// remove the stored value and set state to undefined
setToken(undefined);type Profile = { id: number; name: string };
const serializer = (_key: string, value: Profile) => JSON.stringify({ root: value });
const deserializer = (_key: string, rawValue: string): Profile | undefined => {
const parsed = JSON.parse(rawValue);
return parsed?.root;
};
const [profile, setProfile] = useLocalStorage<Profile>(
'user:profile',
{ id: 1, name: 'Ada' },
{ serializer, deserializer },
);const [filter, setFilter] = useLocalStorage<string>('list:filter', '', {
watch: true,
});-
Initialization
- On mount, reads the
rawstring fromlocalStorage[key]and appliesdeserializer. - If
deserializerreturnsundefined,initialValueis used.
- On mount, reads the
-
Update
setValue(next)writes a new string (viaserializer) or removes the key ifnext === undefined.- A functional updater receives the current value
prevand must return the next one.
-
Key change
- When
keychanges, the hook re‑reads the value for the new key, applyingdeserializeragain.
- When
-
Cross‑tab synchronization (if
watch: true)- Updates state on the
storageevent when bothkeyandstorageAreamatch.
- Updates state on the
- User preferences (theme, language, filters, layouts).
- Small caches and flags (last opened tab, “has the user seen the banner”).
- Form persistence and drafts across reloads.
- Large volumes or complex structures (prefer
IndexedDB). - Sensitive data without encryption.
- State that shouldn’t survive a reload.
-
Inconsistent storage format
- Changing serialization/deserialization logic may break old values — plan migrations.
-
Expecting
initialValueaftersetValue(undefined)- In the current mount,
valuebecomesundefined.initialValueis applied only on the first mount.
- In the current mount,
-
Forgetting
watchfor a shared key- If a key is edited from another tab, enable
watch: trueto get automatic updates.
- If a key is edited from another tab, enable
Exported types
UseLocalStorageReturn<Value>—[value: Value, setValue: (action) => void].UseLocalStorageOptions<Value>— options forserializer/deserializerandwatch.UseLocalStorageSetAction<Value>—Value | ((prev: Value) => Value).