useStorage()
Live demo: useStorage
Sync React state with `localStorage` (or any `Storage`) with JSON and optional `remove`.
Loading demo...
Demo sourceJSX
Overview
useStorage behaves like useState, but synchronizes the value with a storage key (localStorage by default) so state survives page reloads. On mount it reads and parses an existing value (or falls back to initialValue), then writes updates via a serializer; it also exposes remove to clear storage and reset local state back to the initial value.
What it accepts
key: string.initialValue: T.options: UseStorageOptions<T> = {}.
What it returns
- Position 1:
T. - Position 2:
(next: T) => void. - Position 3:
() => void.
Usage
Real-world example: persist UI preferences in localStorage with a custom parser/serializer.
tsx
import useStorage from '@dedalik/use-react/useStorage'
type Preferences = {
theme: 'light' | 'dark'
compactMode: boolean
}
function Example() {
const [prefs, setPrefs, clearPrefs] = useStorage<Preferences>(
'demo:preferences',
{ theme: 'light', compactMode: false },
{
serializer: (value) => JSON.stringify(value),
parser: (raw) => JSON.parse(raw) as Preferences,
storage: window.localStorage,
},
)
return (
<div>
<h3>Preferences</h3>
<p>Theme: {prefs.theme}</p>
<p>Compact mode: {prefs.compactMode ? 'Enabled' : 'Disabled'}</p>
<div style={{ display: 'flex', gap: 8 }}>
<button type='button' onClick={() => setPrefs({ ...prefs, theme: prefs.theme === 'light' ? 'dark' : 'light' })}>
Toggle theme
</button>
<button type='button' onClick={() => setPrefs({ ...prefs, compactMode: !prefs.compactMode })}>
Toggle compact mode
</button>
<button type='button' onClick={clearPrefs}>
Reset preferences
</button>
</div>
</div>
)
}
export default function Demo() {
return <Example />
}API Reference
useStorage
Signature: useStorage(key: string, initialValue: T, options: UseStorageOptions<T> = {}): [T, (next: T) => void, () => void]
Parameters
key(string) - See type in signature.initialValue(T) - See type in signature.options(optionalUseStorageOptions<T>) - See type in signature. Default:{}.
Returns
Tuple:
T(next: T) => void() => void
Copy-paste hook
tsx
import { useCallback, useEffect, useRef, useState } from 'react'
export interface UseStorageOptions<T> {
serializer?: (value: T) => string
parser?: (raw: string) => T
storage?: Storage
}
/**
* Persists state in storage under a key.
*/
export default function useStorage<T>(
key: string,
initialValue: T,
options: UseStorageOptions<T> = {},
): [T, (next: T) => void, () => void] {
const { serializer = JSON.stringify, parser = JSON.parse, storage = window.localStorage } = options
const skipPersistRef = useRef(false)
const [value, setValue] = useState<T>(() => {
try {
const raw = storage.getItem(key)
return raw == null ? initialValue : parser(raw)
} catch {
return initialValue
}
})
useEffect(() => {
if (skipPersistRef.current) {
skipPersistRef.current = false
return
}
try {
storage.setItem(key, serializer(value))
} catch {
// ignore storage write failures
}
}, [key, serializer, storage, value])
const set = useCallback((next: T) => {
setValue(next)
}, [])
const remove = useCallback(() => {
try {
storage.removeItem(key)
} catch {
// ignore storage remove failures
}
skipPersistRef.current = true
setValue(initialValue)
}, [initialValue, key, storage])
return [value, set, remove]
}js
import { useCallback, useEffect, useRef, useState } from 'react'
export default function useStorage(key, initialValue, options = {}) {
const { serializer = JSON.stringify, parser = JSON.parse, storage = window.localStorage } = options
const skipPersistRef = useRef(false)
const [value, setValue] = useState(() => {
try {
const raw = storage.getItem(key)
return raw == null ? initialValue : parser(raw)
} catch {
return initialValue
}
})
useEffect(() => {
if (skipPersistRef.current) {
skipPersistRef.current = false
return
}
try {
storage.setItem(key, serializer(value))
} catch {
// ignore storage write failures
}
}, [key, serializer, storage, value])
const set = useCallback((next) => {
setValue(next)
}, [])
const remove = useCallback(() => {
try {
storage.removeItem(key)
} catch {
// ignore storage remove failures
}
skipPersistRef.current = true
setValue(initialValue)
}, [initialValue, key, storage])
return [value, set, remove]
}