A React hook for persisting state in localStorage with automatic synchronization
npm install @usefy/use-local-storage

A powerful React hook for persisting state in localStorage with automatic same-tab and cross-tab synchronization
Installation •
Quick Start •
API Reference •
Examples •
License
---
@usefy/use-local-storage provides a useState-like API for persisting data in localStorage. Features include same-tab component synchronization, cross-tab synchronization, custom serialization, lazy initialization, error handling, and a removeValue function. Data persists across browser sessions.
Part of the @usefy ecosystem — a collection of production-ready React hooks designed for modern applications.
- Zero Dependencies — Pure React implementation with no external dependencies
- TypeScript First — Full type safety with generics and exported interfaces
- useState-like API — Familiar tuple return: [value, setValue, removeValue]
- Same-Tab Sync — Multiple components using the same key stay in sync automatically
- Cross-Tab Sync — Automatic synchronization across browser tabs
- React 18+ Optimized — Built with useSyncExternalStore for Concurrent Mode compatibility
- Custom Serialization — Support for Date, Map, Set, or any custom type
- Lazy Initialization — Function initializer support for expensive defaults
- Error Handling — onError callback for graceful error recovery
- SSR Compatible — Works seamlessly with Next.js, Remix, and other SSR frameworks
- Stable References — Memoized functions for optimal performance
- Well Tested — Comprehensive test coverage with Vitest
---
``bashnpm
npm install @usefy/use-local-storage
$3
This package requires React 18 or 19:
`json
{
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
}
}
`---
Quick Start
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function ThemeToggle() {
const [theme, setTheme, removeTheme] = useLocalStorage("theme", "light");
return (
Current theme: {theme}
);
}
`---
API Reference
$3
A hook that persists state in localStorage with automatic synchronization.
#### Parameters
| Parameter | Type | Description |
| -------------- | --------------------------- | ------------------------------------------ |
|
key | string | The localStorage key |
| initialValue | T \| () => T | Initial value or lazy initializer function |
| options | UseLocalStorageOptions | Configuration options |#### Options
| Option | Type | Default | Description |
| -------------- | ------------------------ | ---------------- | ------------------------------ |
|
serializer | (value: T) => string | JSON.stringify | Custom serializer function |
| deserializer | (value: string) => T | JSON.parse | Custom deserializer function |
| syncTabs | boolean | true | Sync value across browser tabs |
| onError | (error: Error) => void | — | Callback for error handling |#### Returns
[T, SetValue| Index | Type | Description |
| ----- | ----------------------------- | --------------------------------------------- |
|
[0] | T | Current stored value |
| [1] | Dispatch | Function to update value (same as useState) |
| [2] | () => void | Function to remove value and reset to initial |---
Examples
$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function NameInput() {
const [name, setName, removeName] = useLocalStorage("userName", "");
return (
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name"
/>
Hello, {name || "Guest"}!
);
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";interface UserSettings {
notifications: boolean;
language: string;
fontSize: number;
}
function SettingsPanel() {
const [settings, setSettings] = useLocalStorage("settings", {
notifications: true,
language: "en",
fontSize: 16,
});
return (
value={settings.language}
onChange={(e) =>
setSettings((prev) => ({ ...prev, language: e.target.value }))
}
>
type="range"
min="12"
max="24"
value={settings.fontSize}
onChange={(e) =>
setSettings((prev) => ({ ...prev, fontSize: +e.target.value }))
}
/>
);
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function ExpensiveDefaultDemo() {
// Expensive computation only runs if no stored value exists
const [config, setConfig] = useLocalStorage("appConfig", () => {
console.log("Computing expensive default...");
return generateComplexDefaultConfig();
});
return ;
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function DatePicker() {
const [lastVisit, setLastVisit] = useLocalStorage(
"lastVisit",
new Date(),
{
serializer: (date) => date.toISOString(),
deserializer: (str) => new Date(str),
}
);
return (
Last visit: {lastVisit.toLocaleDateString()}
);
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function FavoritesManager() {
const [favorites, setFavorites] = useLocalStorage
const toggleFavorite = (id: string) => {
setFavorites((prev) => {
const next = new Map(prev);
next.has(id) ? next.delete(id) : next.set(id, true);
return next;
});
};
return (
{items.map((item) => (
{item.name}
))}
);
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function RobustStorage() {
const [data, setData] = useLocalStorage(
"userData",
{ items: [] },
{
onError: (error) => {
console.error("Storage error:", error.message);
// Could show toast notification, log to analytics, etc.
toast.error("Failed to save data. Storage may be full.");
},
}
);
return ;
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";// Multiple components using the same key automatically stay in sync!
function Navbar() {
const [user] = useLocalStorage("user", null);
return Welcome, {user?.name || "Guest"};
}
function LoginForm() {
const [, setUser] = useLocalStorage("user", null);
const handleLogin = async (credentials: Credentials) => {
const user = await authenticate(credentials);
setUser(user); // Navbar automatically updates!
};
return ;
}
function Sidebar() {
const [user] = useLocalStorage("user", null);
// Also updates when LoginForm calls setUser!
return user ? : ;
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function CartCounter() {
// Changes in one tab automatically sync to other tabs
const [cartItems, setCartItems] = useLocalStorage("cart", []);
const addItem = (item: string) => {
setCartItems((prev) => [...prev, item]);
};
return (
Cart: {cartItems.length} items
{/ Other tabs will see the updated count /}
);
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";function LocalOnlyData() {
// Don't sync changes from other tabs
const [draft, setDraft] = useLocalStorage("draft", "", {
syncTabs: false,
});
return (
value={draft}
onChange={(e) => setDraft(e.target.value)}
placeholder="This draft won't sync with other tabs"
/>
);
}
`$3
`tsx
import { useLocalStorage } from "@usefy/use-local-storage";interface CartItem {
id: string;
name: string;
quantity: number;
price: number;
}
function ShoppingCart() {
const [cart, setCart, clearCart] = useLocalStorage("cart", []);
const addToCart = (product: Product) => {
setCart((prev) => {
const existing = prev.find((item) => item.id === product.id);
if (existing) {
return prev.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
);
}
return [...prev, { ...product, quantity: 1 }];
});
};
const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
return (
Cart ({cart.length} items)
{cart.map((item) => (
{item.name} x {item.quantity} - ${item.price * item.quantity}
))}
Total: ${total.toFixed(2)}
);
}
`---
TypeScript
This hook is written in TypeScript with full generic support.
`tsx
import {
useLocalStorage,
type UseLocalStorageOptions,
type UseLocalStorageReturn,
type InitialValue,
} from "@usefy/use-local-storage";// Generic type inference
const [name, setName] = useLocalStorage("name", "John"); // string
const [count, setCount] = useLocalStorage("count", 0); // number
const [items, setItems] = useLocalStorage("items", [1, 2]); // number[]
// Explicit generic type
interface User {
id: string;
name: string;
}
const [user, setUser] = useLocalStorage("user", null);
``---
This package maintains comprehensive test coverage to ensure reliability and stability.
📊 View Detailed Coverage Report (GitHub Pages)
Initialization Tests
- Return initial value when localStorage is empty
- Return stored value when localStorage has data
- Support lazy initialization with function
- Not call initializer when localStorage has data
- Fallback to initial value when JSON parse fails
- Call onError when localStorage read fails
Same-Tab Sync Tests
- Sync ComponentB when ComponentA updates the same key
- Sync multiple components using the same key
- Sync when using functional updates
- Sync when removeValue is called
- Not affect components with different keys
- Handle rapid updates from different components
- Cleanup listeners on unmount
Cross-Tab Sync Tests
- Update value when storage event is fired
- Reset to initial when key is removed in another tab
- Ignore storage events for different keys
- Not sync when syncTabs is false
- Fallback to initial value on invalid JSON
---
MIT © mirunamu
This package is part of the usefy monorepo.
---
Built with care by the usefy team