useLocalStorage
Persist and sync state with localStorage for reliable client-side storage in React.
useLocalStorage
Persist and sync state with localStorage for reliable client-side storage in React.
useLocalStorage Demo
Your name is saved in localStorage and will persist across page reloads.
Installation
npx open-hook add useLocalStorageyarn dlx open-hook add useLocalStoragepnpm dlx open-hook add useLocalStorageAPI Reference
Parameters
| Prop | Type | Default |
|---|---|---|
initialValue? | T | - |
key? | string | - |
Returns
| Prop | Type | Default |
|---|---|---|
setValue? | (value: T | ((val: T) => T)) => void | - |
value? | T | - |
TypeScript Signature
function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void];Advanced Usage Examples
// Simple string value
const [username, setUsername] = useLocalStorage("username", "");
// Boolean preference
const [darkMode, setDarkMode] = useLocalStorage("darkMode", false);
// Number setting
const [fontSize, setFontSize] = useLocalStorage("fontSize", 16);interface UserProfile {
name: string;
email: string;
preferences: {
theme: string;
language: string;
};
}
const [profile, setProfile] = useLocalStorage<UserProfile>("userProfile", {
name: "",
email: "",
preferences: {
theme: "light",
language: "en",
},
});
// Update nested properties
const updateTheme = (newTheme: string) => {
setProfile((prev) => ({
...prev,
preferences: {
...prev.preferences,
theme: newTheme,
},
}));
};interface FormData {
title: string;
content: string;
tags: string[];
}
function DraftEditor() {
const [formData, setFormData] = useLocalStorage<FormData>("draft", {
title: "",
content: "",
tags: [],
});
const handleSubmit = () => {
// Submit form
submitPost(formData);
// Clear draft after successful submission
setFormData({
title: "",
content: "",
tags: [],
});
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.title}
onChange={(e) =>
setFormData((prev) => ({
...prev,
title: e.target.value,
}))
}
placeholder="Title"
/>
{/* More form fields */}
</form>
);
}function useLocalStorageWithSync<T>(key: string, initialValue: T) {
const [value, setValue] = useLocalStorage(key, initialValue);
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key && e.newValue !== null) {
setValue(JSON.parse(e.newValue));
}
};
window.addEventListener("storage", handleStorageChange);
return () => window.removeEventListener("storage", handleStorageChange);
}, [key, setValue]);
return [value, setValue];
}Best Practices
Choose appropriate storage strategies based on your use case:
// Theme preferences - simple and persistent
const [theme, setTheme] = useLocalStorage("app-theme", "light");
// User settings - complex object with defaults
const [settings, setSettings] = useLocalStorage("user-settings", {
notifications: true,
autoSave: true,
language: "en",
});
// Form drafts - auto-save with cleanup
const [draft, setDraft] = useLocalStorage("post-draft", {
title: "",
content: "",
});Security & Performance Guidelines
- 🔒 Data Security: Never store sensitive data like passwords or tokens
- 📦 Size Limits: Keep data under 5MB (localStorage limit varies by browser)
- 🚀 Performance: Use for small to medium-sized data only
- 🔄 Serialization: Ensure data is JSON-serializable
Do's and Don'ts
- ✅ Use for user preferences and settings
- ✅ Store theme, language, and UI state
- ✅ Implement form draft functionality
- ✅ Cache non-sensitive application data
- ❌ Don't store passwords or authentication tokens
- ❌ Avoid storing large files or images
- ❌ Don't rely on localStorage for critical application state
- ❌ Avoid circular references in objects
Performance Metrics
| Metric | Value | Description |
|---|---|---|
| Bundle Size | ~0.7kb | Minified + gzipped |
| Persistence | Permanent | Survives browser restarts |
| Sync Speed | Instant | Immediate localStorage updates |
| Storage Limit | ~5-10MB | Browser-dependent limit |
Browser Compatibility
| Browser | Supported | Notes |
|---|---|---|
| Chrome (latest) | ✅Yes | Full support |
| Firefox (latest) | ✅Yes | Full support |
| Safari (latest) | ✅Yes | Full support |
| Edge (latest) | ✅Yes | Full support |
| IE 11 | ✅Yes | Basic support |
| Node.js | ❌No | SSR-safe with fallback |
Use Cases
Theme & Preferences
Store user interface preferences like theme, language, and layout settings.
Form Draft Saving
Automatically save form data to prevent data loss on page refresh.
Shopping Cart Persistence
Maintain shopping cart contents across browser sessions.
Application Settings
Store non-sensitive application configuration and user customizations.
Accessibility
Accessibility Considerations
- localStorage doesn't directly impact accessibility - Use for storing accessibility preferences (font size, contrast settings) - Ensure UI updates from localStorage are announced to screen readers - Consider high contrast and reduced motion preferences
Troubleshooting
Internals
How It Works
The hook initializes state by reading from localStorage on mount, parsing the JSON value, and falling back to the initial value if parsing fails or localStorage is unavailable. When the value changes, it immediately updates both React state and localStorage using JSON.stringify().
The hook includes SSR safety by checking for window availability and graceful error handling for localStorage quota exceeded or parsing errors.
Testing Example
import { renderHook, act } from "@testing-library/react";
import { useLocalStorage } from "./useLocalStorage";
// Mock localStorage
const localStorageMock = {
getItem: jest.fn(),
setItem: jest.fn(),
removeItem: jest.fn(),
clear: jest.fn(),
};
global.localStorage = localStorageMock as any;
describe("useLocalStorage", () => {
beforeEach(() => {
localStorageMock.getItem.mockClear();
localStorageMock.setItem.mockClear();
});
it("should return initial value when localStorage is empty", () => {
localStorageMock.getItem.mockReturnValue(null);
const { result } = renderHook(() => useLocalStorage("test-key", "default"));
expect(result.current[0]).toBe("default");
});
it("should persist value to localStorage", () => {
const { result } = renderHook(() => useLocalStorage("test-key", "default"));
act(() => {
result.current[1]("new-value");
});
expect(localStorageMock.setItem).toHaveBeenCalledWith(
"test-key",
JSON.stringify("new-value")
);
expect(result.current[0]).toBe("new-value");
});
it("should handle function updates", () => {
const { result } = renderHook(() => useLocalStorage("test-key", 0));
act(() => {
result.current[1]((prev) => prev + 1);
});
expect(result.current[0]).toBe(1);
});
});FAQ
Related Hooks
- useSessionStorage - Session-only storage alternative
- useDebounce - Debounce localStorage updates
- usePrevious - Track previous localStorage values
Changelog
- 2.0.0 — Enhanced TypeScript support, added functional updates, improved SSR handling, better error handling, and comprehensive documentation
- 1.2.0 — Added support for complex objects and arrays with better JSON serialization
- 1.1.0 — Improved error handling and SSR compatibility
- 1.0.0 — Initial release with basic localStorage persistence functionality