@usefy/use-local-storage 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +540 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,540 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/geon0529/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
3
+ </p>
4
+
5
+ <h1 align="center">@usefy/use-local-storage</h1>
6
+
7
+ <p align="center">
8
+ <strong>A powerful React hook for persisting state in localStorage with automatic cross-tab synchronization</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@usefy/use-local-storage">
13
+ <img src="https://img.shields.io/npm/v/@usefy/use-local-storage.svg?style=flat-square&color=007acc" alt="npm version" />
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/@usefy/use-local-storage">
16
+ <img src="https://img.shields.io/npm/dm/@usefy/use-local-storage.svg?style=flat-square&color=007acc" alt="npm downloads" />
17
+ </a>
18
+ <a href="https://bundlephobia.com/package/@usefy/use-local-storage">
19
+ <img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-local-storage?style=flat-square&color=007acc" alt="bundle size" />
20
+ </a>
21
+ <a href="https://github.com/geon0529/usefy/blob/master/LICENSE">
22
+ <img src="https://img.shields.io/npm/l/@usefy/use-local-storage.svg?style=flat-square&color=007acc" alt="license" />
23
+ </a>
24
+ </p>
25
+
26
+ <p align="center">
27
+ <a href="#installation">Installation</a> •
28
+ <a href="#quick-start">Quick Start</a> •
29
+ <a href="#api-reference">API Reference</a> •
30
+ <a href="#examples">Examples</a> •
31
+ <a href="#license">License</a>
32
+ </p>
33
+
34
+ ---
35
+
36
+ ## Overview
37
+
38
+ `@usefy/use-local-storage` provides a `useState`-like API for persisting data in localStorage. Features include cross-tab synchronization, custom serialization, lazy initialization, error handling, and a `removeValue` function. Data persists across browser sessions.
39
+
40
+ **Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
41
+
42
+ ### Why use-local-storage?
43
+
44
+ - **Zero Dependencies** — Pure React implementation with no external dependencies
45
+ - **TypeScript First** — Full type safety with generics and exported interfaces
46
+ - **useState-like API** — Familiar tuple return: `[value, setValue, removeValue]`
47
+ - **Cross-Tab Sync** — Automatic synchronization across browser tabs
48
+ - **Custom Serialization** — Support for Date, Map, Set, or any custom type
49
+ - **Lazy Initialization** — Function initializer support for expensive defaults
50
+ - **Error Handling** — `onError` callback for graceful error recovery
51
+ - **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
52
+ - **Stable References** — Memoized functions for optimal performance
53
+ - **Well Tested** — Comprehensive test coverage with Vitest
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ # npm
61
+ npm install @usefy/use-local-storage
62
+
63
+ # yarn
64
+ yarn add @usefy/use-local-storage
65
+
66
+ # pnpm
67
+ pnpm add @usefy/use-local-storage
68
+ ```
69
+
70
+ ### Peer Dependencies
71
+
72
+ This package requires React 18 or 19:
73
+
74
+ ```json
75
+ {
76
+ "peerDependencies": {
77
+ "react": "^18.0.0 || ^19.0.0"
78
+ }
79
+ }
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Quick Start
85
+
86
+ ```tsx
87
+ import { useLocalStorage } from '@usefy/use-local-storage';
88
+
89
+ function ThemeToggle() {
90
+ const [theme, setTheme, removeTheme] = useLocalStorage('theme', 'light');
91
+
92
+ return (
93
+ <div>
94
+ <p>Current theme: {theme}</p>
95
+ <button onClick={() => setTheme('dark')}>Dark</button>
96
+ <button onClick={() => setTheme('light')}>Light</button>
97
+ <button onClick={removeTheme}>Reset</button>
98
+ </div>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ---
104
+
105
+ ## API Reference
106
+
107
+ ### `useLocalStorage<T>(key, initialValue, options?)`
108
+
109
+ A hook that persists state in localStorage with automatic synchronization.
110
+
111
+ #### Parameters
112
+
113
+ | Parameter | Type | Description |
114
+ |-----------|------|-------------|
115
+ | `key` | `string` | The localStorage key |
116
+ | `initialValue` | `T \| () => T` | Initial value or lazy initializer function |
117
+ | `options` | `UseLocalStorageOptions<T>` | Configuration options |
118
+
119
+ #### Options
120
+
121
+ | Option | Type | Default | Description |
122
+ |--------|------|---------|-------------|
123
+ | `serializer` | `(value: T) => string` | `JSON.stringify` | Custom serializer function |
124
+ | `deserializer` | `(value: string) => T` | `JSON.parse` | Custom deserializer function |
125
+ | `syncTabs` | `boolean` | `true` | Sync value across browser tabs |
126
+ | `onError` | `(error: Error) => void` | — | Callback for error handling |
127
+
128
+ #### Returns `[T, SetValue<T>, RemoveValue]`
129
+
130
+ | Index | Type | Description |
131
+ |-------|------|-------------|
132
+ | `[0]` | `T` | Current stored value |
133
+ | `[1]` | `Dispatch<SetStateAction<T>>` | Function to update value (same as useState) |
134
+ | `[2]` | `() => void` | Function to remove value and reset to initial |
135
+
136
+ ---
137
+
138
+ ## Examples
139
+
140
+ ### Basic String Storage
141
+
142
+ ```tsx
143
+ import { useLocalStorage } from '@usefy/use-local-storage';
144
+
145
+ function NameInput() {
146
+ const [name, setName, removeName] = useLocalStorage('userName', '');
147
+
148
+ return (
149
+ <div>
150
+ <input
151
+ value={name}
152
+ onChange={(e) => setName(e.target.value)}
153
+ placeholder="Enter your name"
154
+ />
155
+ <button onClick={removeName}>Clear</button>
156
+ <p>Hello, {name || 'Guest'}!</p>
157
+ </div>
158
+ );
159
+ }
160
+ ```
161
+
162
+ ### Object State
163
+
164
+ ```tsx
165
+ import { useLocalStorage } from '@usefy/use-local-storage';
166
+
167
+ interface UserSettings {
168
+ notifications: boolean;
169
+ language: string;
170
+ fontSize: number;
171
+ }
172
+
173
+ function SettingsPanel() {
174
+ const [settings, setSettings] = useLocalStorage<UserSettings>('settings', {
175
+ notifications: true,
176
+ language: 'en',
177
+ fontSize: 16,
178
+ });
179
+
180
+ return (
181
+ <div>
182
+ <label>
183
+ <input
184
+ type="checkbox"
185
+ checked={settings.notifications}
186
+ onChange={(e) =>
187
+ setSettings((prev) => ({ ...prev, notifications: e.target.checked }))
188
+ }
189
+ />
190
+ Enable Notifications
191
+ </label>
192
+
193
+ <select
194
+ value={settings.language}
195
+ onChange={(e) =>
196
+ setSettings((prev) => ({ ...prev, language: e.target.value }))
197
+ }
198
+ >
199
+ <option value="en">English</option>
200
+ <option value="ko">한국어</option>
201
+ <option value="ja">日本語</option>
202
+ </select>
203
+
204
+ <input
205
+ type="range"
206
+ min="12"
207
+ max="24"
208
+ value={settings.fontSize}
209
+ onChange={(e) =>
210
+ setSettings((prev) => ({ ...prev, fontSize: +e.target.value }))
211
+ }
212
+ />
213
+ </div>
214
+ );
215
+ }
216
+ ```
217
+
218
+ ### Lazy Initialization
219
+
220
+ ```tsx
221
+ import { useLocalStorage } from '@usefy/use-local-storage';
222
+
223
+ function ExpensiveDefaultDemo() {
224
+ // Expensive computation only runs if no stored value exists
225
+ const [config, setConfig] = useLocalStorage('appConfig', () => {
226
+ console.log('Computing expensive default...');
227
+ return generateComplexDefaultConfig();
228
+ });
229
+
230
+ return <ConfigEditor config={config} onChange={setConfig} />;
231
+ }
232
+ ```
233
+
234
+ ### Custom Serialization (Date)
235
+
236
+ ```tsx
237
+ import { useLocalStorage } from '@usefy/use-local-storage';
238
+
239
+ function DatePicker() {
240
+ const [lastVisit, setLastVisit] = useLocalStorage<Date>(
241
+ 'lastVisit',
242
+ new Date(),
243
+ {
244
+ serializer: (date) => date.toISOString(),
245
+ deserializer: (str) => new Date(str),
246
+ }
247
+ );
248
+
249
+ return (
250
+ <div>
251
+ <p>Last visit: {lastVisit.toLocaleDateString()}</p>
252
+ <button onClick={() => setLastVisit(new Date())}>Update</button>
253
+ </div>
254
+ );
255
+ }
256
+ ```
257
+
258
+ ### Custom Serialization (Map)
259
+
260
+ ```tsx
261
+ import { useLocalStorage } from '@usefy/use-local-storage';
262
+
263
+ function FavoritesManager() {
264
+ const [favorites, setFavorites] = useLocalStorage<Map<string, boolean>>(
265
+ 'favorites',
266
+ new Map(),
267
+ {
268
+ serializer: (map) => JSON.stringify([...map.entries()]),
269
+ deserializer: (str) => new Map(JSON.parse(str)),
270
+ }
271
+ );
272
+
273
+ const toggleFavorite = (id: string) => {
274
+ setFavorites((prev) => {
275
+ const next = new Map(prev);
276
+ next.has(id) ? next.delete(id) : next.set(id, true);
277
+ return next;
278
+ });
279
+ };
280
+
281
+ return (
282
+ <ul>
283
+ {items.map((item) => (
284
+ <li key={item.id}>
285
+ {item.name}
286
+ <button onClick={() => toggleFavorite(item.id)}>
287
+ {favorites.has(item.id) ? '★' : '☆'}
288
+ </button>
289
+ </li>
290
+ ))}
291
+ </ul>
292
+ );
293
+ }
294
+ ```
295
+
296
+ ### Error Handling
297
+
298
+ ```tsx
299
+ import { useLocalStorage } from '@usefy/use-local-storage';
300
+
301
+ function RobustStorage() {
302
+ const [data, setData] = useLocalStorage('userData', { items: [] }, {
303
+ onError: (error) => {
304
+ console.error('Storage error:', error.message);
305
+ // Could show toast notification, log to analytics, etc.
306
+ toast.error('Failed to save data. Storage may be full.');
307
+ },
308
+ });
309
+
310
+ return <DataEditor data={data} onChange={setData} />;
311
+ }
312
+ ```
313
+
314
+ ### Cross-Tab Synchronization
315
+
316
+ ```tsx
317
+ import { useLocalStorage } from '@usefy/use-local-storage';
318
+
319
+ function CartCounter() {
320
+ // Changes in one tab automatically sync to other tabs
321
+ const [cartItems, setCartItems] = useLocalStorage<string[]>('cart', []);
322
+
323
+ const addItem = (item: string) => {
324
+ setCartItems((prev) => [...prev, item]);
325
+ };
326
+
327
+ return (
328
+ <div>
329
+ <span>Cart: {cartItems.length} items</span>
330
+ {/* Other tabs will see the updated count */}
331
+ </div>
332
+ );
333
+ }
334
+ ```
335
+
336
+ ### Disable Tab Sync
337
+
338
+ ```tsx
339
+ import { useLocalStorage } from '@usefy/use-local-storage';
340
+
341
+ function LocalOnlyData() {
342
+ // Don't sync changes from other tabs
343
+ const [draft, setDraft] = useLocalStorage('draft', '', {
344
+ syncTabs: false,
345
+ });
346
+
347
+ return (
348
+ <textarea
349
+ value={draft}
350
+ onChange={(e) => setDraft(e.target.value)}
351
+ placeholder="This draft won't sync with other tabs"
352
+ />
353
+ );
354
+ }
355
+ ```
356
+
357
+ ### Shopping Cart
358
+
359
+ ```tsx
360
+ import { useLocalStorage } from '@usefy/use-local-storage';
361
+
362
+ interface CartItem {
363
+ id: string;
364
+ name: string;
365
+ quantity: number;
366
+ price: number;
367
+ }
368
+
369
+ function ShoppingCart() {
370
+ const [cart, setCart, clearCart] = useLocalStorage<CartItem[]>('cart', []);
371
+
372
+ const addToCart = (product: Product) => {
373
+ setCart((prev) => {
374
+ const existing = prev.find((item) => item.id === product.id);
375
+ if (existing) {
376
+ return prev.map((item) =>
377
+ item.id === product.id
378
+ ? { ...item, quantity: item.quantity + 1 }
379
+ : item
380
+ );
381
+ }
382
+ return [...prev, { ...product, quantity: 1 }];
383
+ });
384
+ };
385
+
386
+ const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
387
+
388
+ return (
389
+ <div>
390
+ <h2>Cart ({cart.length} items)</h2>
391
+ <ul>
392
+ {cart.map((item) => (
393
+ <li key={item.id}>
394
+ {item.name} x {item.quantity} - ${item.price * item.quantity}
395
+ </li>
396
+ ))}
397
+ </ul>
398
+ <p>Total: ${total.toFixed(2)}</p>
399
+ <button onClick={clearCart}>Clear Cart</button>
400
+ </div>
401
+ );
402
+ }
403
+ ```
404
+
405
+ ---
406
+
407
+ ## TypeScript
408
+
409
+ This hook is written in TypeScript with full generic support.
410
+
411
+ ```tsx
412
+ import {
413
+ useLocalStorage,
414
+ type UseLocalStorageOptions,
415
+ type UseLocalStorageReturn,
416
+ type InitialValue,
417
+ } from '@usefy/use-local-storage';
418
+
419
+ // Generic type inference
420
+ const [name, setName] = useLocalStorage('name', 'John'); // string
421
+ const [count, setCount] = useLocalStorage('count', 0); // number
422
+ const [items, setItems] = useLocalStorage('items', [1, 2]); // number[]
423
+
424
+ // Explicit generic type
425
+ interface User {
426
+ id: string;
427
+ name: string;
428
+ }
429
+ const [user, setUser] = useLocalStorage<User | null>('user', null);
430
+ ```
431
+
432
+ ---
433
+
434
+ ## Testing
435
+
436
+ This package maintains comprehensive test coverage to ensure reliability and stability.
437
+
438
+ ### Test Coverage
439
+
440
+ | Category | Tests | Coverage |
441
+ |----------|-------|----------|
442
+ | Initialization | 7 | 100% |
443
+ | setValue | 5 | 100% |
444
+ | removeValue | 2 | 100% |
445
+ | Type Preservation | 5 | 100% |
446
+ | Custom Serialization | 2 | 100% |
447
+ | Cross-Tab Sync | 5 | 100% |
448
+ | Key Changes | 2 | 100% |
449
+ | Function Stability | 3 | 100% |
450
+ | Multiple Instances | 2 | 100% |
451
+ | Edge Cases | 6 | 100% |
452
+ | Cleanup | 1 | 100% |
453
+ | **Total** | **40** | **95.08%** |
454
+
455
+ ### Test Categories
456
+
457
+ <details>
458
+ <summary><strong>Initialization Tests</strong></summary>
459
+
460
+ - Return initial value when localStorage is empty
461
+ - Return stored value when localStorage has data
462
+ - Support lazy initialization with function
463
+ - Not call initializer when localStorage has data
464
+ - Fallback to initial value when JSON parse fails
465
+ - Call onError when localStorage read fails
466
+
467
+ </details>
468
+
469
+ <details>
470
+ <summary><strong>Cross-Tab Sync Tests</strong></summary>
471
+
472
+ - Update value when storage event is fired
473
+ - Reset to initial when key is removed in another tab
474
+ - Ignore storage events for different keys
475
+ - Not sync when syncTabs is false
476
+ - Fallback to initial value on invalid JSON
477
+
478
+ </details>
479
+
480
+ ### Running Tests
481
+
482
+ ```bash
483
+ # Run all tests
484
+ pnpm test
485
+
486
+ # Run tests in watch mode
487
+ pnpm test:watch
488
+
489
+ # Run tests with coverage report
490
+ pnpm test --coverage
491
+ ```
492
+
493
+ ---
494
+
495
+ ## Related Packages
496
+
497
+ Explore other hooks in the **@usefy** collection:
498
+
499
+ | Package | Description |
500
+ |---------|-------------|
501
+ | [@usefy/use-session-storage](https://www.npmjs.com/package/@usefy/use-session-storage) | Session storage persistence |
502
+ | [@usefy/use-toggle](https://www.npmjs.com/package/@usefy/use-toggle) | Boolean state management |
503
+ | [@usefy/use-counter](https://www.npmjs.com/package/@usefy/use-counter) | Counter state management |
504
+ | [@usefy/use-debounce](https://www.npmjs.com/package/@usefy/use-debounce) | Value debouncing |
505
+ | [@usefy/use-throttle](https://www.npmjs.com/package/@usefy/use-throttle) | Value throttling |
506
+ | [@usefy/use-click-any-where](https://www.npmjs.com/package/@usefy/use-click-any-where) | Global click detection |
507
+
508
+ ---
509
+
510
+ ## Contributing
511
+
512
+ We welcome contributions! Please see our [Contributing Guide](https://github.com/geon0529/usefy/blob/master/CONTRIBUTING.md) for details.
513
+
514
+ ```bash
515
+ # Clone the repository
516
+ git clone https://github.com/geon0529/usefy.git
517
+
518
+ # Install dependencies
519
+ pnpm install
520
+
521
+ # Run tests
522
+ pnpm test
523
+
524
+ # Build
525
+ pnpm build
526
+ ```
527
+
528
+ ---
529
+
530
+ ## License
531
+
532
+ MIT © [mirunamu](https://github.com/geon0529)
533
+
534
+ This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
535
+
536
+ ---
537
+
538
+ <p align="center">
539
+ <sub>Built with care by the usefy team</sub>
540
+ </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-local-storage",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "A React hook for persisting state in localStorage with automatic synchronization",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",