useLocalStorage
React side-effect hook that manages a single localStorage key
useLocalStorage binds a React state to a localStorage key. It returns a [value, setValue] tuple similar to useState. The value is read from storage on mount and written back whenever you call setValue. Setting the value to null removes the key. Custom serializers can be provided for non-string data types, and the hook listens for cross-tab storage events by default so that changes in one tab are reflected in others.
When to Use
- Persisting user preferences (theme, language, layout) across page reloads
- Caching form drafts or application state so users can resume where they left off
- Sharing simple state between browser tabs via the
storageevent
Notes
- Persistence: Data survives page reloads and browser restarts. Use
useSessionStorageif you only need data for the current session. - Cross-tab & same-tab sync: Every component bound to the same key stays in sync — within the current tab (always on, no reload) and across other tabs (via the
storageevent).listenToStorageChanges: falsedisables only the cross-tab listener; same-tab sync stays active. - Custom serialization: For objects or non-string values, provide
serializer.readandserializer.writefunctions in the options. The default behavior uses JSON serialization for objects and raw strings for string values. - See also
useSessionStoragefor session-scoped storage anduseCookiefor cookie-based persistence.
Usage
Live Editor
function Demo() { // bind string const [value, setValue] = useLocalStorage("my-key", "key"); // bind object with custom serializer const [myObj, setMyObj] = useLocalStorage( "myObj", { name: "test", }, { serializer: { read: (val) => { console.log("read", val); return JSON.parse(val); }, write: (val) => { console.log("write", val); return JSON.stringify(val); }, }, } ); return ( <div> <div> <h3>String Value</h3> <div>Value: {value}</div> <button onClick={() => setValue("bar")}>Set to "bar"</button> <button onClick={() => setValue("baz")}>Set to "baz"</button> <button onClick={() => setValue(null)}>Remove</button> </div> <div style={{ marginTop: "20px" }}> <h3>Object Value</h3> <div>Object: {JSON.stringify(myObj)}</div> <button onClick={() => setMyObj({ name: "updated" })}> Update Object </button> <button onClick={() => setMyObj({ name: "test", count: 1 })}> Add Property </button> <button onClick={() => setMyObj(null)}>Remove Object</button> </div> </div> ); };
Result
Same-tab sync
Two components bound to the same key stay in sync within the tab — not just across tabs. Click a button in one panel and the other updates immediately, no reload:
Live Editor
function StoragePanel({ label }) { const [value, setValue] = useLocalStorage("shared-demo-key", "A"); return ( <div style={{ border: "1px solid var(--sl-color-gray-5, #ccc)", borderRadius: 6, padding: 12, marginBottom: 8, }} > <strong>{label}</strong> reads: <code>{String(value ?? "(empty)")}</code> <div style={{ display: "flex", gap: 8, marginTop: 8 }}> <button onClick={() => setValue("A")}>Set A</button> <button onClick={() => setValue("B")}>Set B</button> <button onClick={() => setValue(null)}>Clear</button> </div> </div> ); } function Demo() { return ( <div> <p>Click a button in one panel — the other updates in the same tab:</p> <StoragePanel label="Component A" /> <StoragePanel label="Component B" /> </div> ); } render(<Demo />);
Result
API
useLocalStorage
Returns
readonly [T | null, React.Dispatch<React.SetStateAction<T | null>>]: A tuple with the following elements:
- The current value of the localStorage.
- A function to update the value of the localStorage.
Arguments
| Argument | Description | Type | DefaultValue |
|---|---|---|---|
| key | key | string (Required) | - |
| defaultValue | default value | T | undefined | - |
| options | optional params | UseLocalStorageOptions<T> | undefined | - |
UseLocalStorageOptions
| Property | Description | Type | DefaultValue |
|---|---|---|---|
| serializer | Custom data serialization | UseLocalStorageSerializer<T> | - |
| onError | On error callback | (error: unknown) => void | console.error |
| effectStorageValue | set to storage when nodata in first mount, deprecated | T | (() => T) | - |
| mountStorageValue | set to storage when nodata in first mount | T | (() => T) | - |
| listenToStorageChanges | listen to storage changes | boolean | true |
UseLocalStorageSerializer
| Property | Description | Type | DefaultValue |
|---|---|---|---|
| read | Custom data read | (raw: string) => T (Required) | - |
| write | Custom data write | (value: T) => string (Required) | - |