@usefy/use-session-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 +562 -0
  2. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,562 @@
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-session-storage</h1>
6
+
7
+ <p align="center">
8
+ <strong>A lightweight React hook for persisting state in sessionStorage</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/@usefy/use-session-storage">
13
+ <img src="https://img.shields.io/npm/v/@usefy/use-session-storage.svg?style=flat-square&color=007acc" alt="npm version" />
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/@usefy/use-session-storage">
16
+ <img src="https://img.shields.io/npm/dm/@usefy/use-session-storage.svg?style=flat-square&color=007acc" alt="npm downloads" />
17
+ </a>
18
+ <a href="https://bundlephobia.com/package/@usefy/use-session-storage">
19
+ <img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-session-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-session-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-session-storage` provides a `useState`-like API for persisting data in sessionStorage. Data persists during the browser session (tab lifetime) but clears when the tab is closed. Each tab has isolated storage, making it perfect for temporary form data, wizard steps, and session-specific state.
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-session-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
+ - **Tab Isolation** — Each browser tab has its own session storage
48
+ - **Auto-Cleanup** — Data cleared automatically when tab closes
49
+ - **Custom Serialization** — Support for Date, Map, Set, or any custom type
50
+ - **Lazy Initialization** — Function initializer support for expensive defaults
51
+ - **Error Handling** — `onError` callback for graceful error recovery
52
+ - **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
53
+ - **Stable References** — Memoized functions for optimal performance
54
+ - **Well Tested** — Comprehensive test coverage with Vitest
55
+
56
+ ### localStorage vs sessionStorage
57
+
58
+ | Feature | localStorage | sessionStorage |
59
+ |---------|--------------|----------------|
60
+ | Data persistence | Until explicitly cleared | Until tab closes |
61
+ | Tab sharing | Shared across all tabs | Isolated per tab |
62
+ | Best for | User preferences, themes | Form drafts, wizard steps |
63
+
64
+ ---
65
+
66
+ ## Installation
67
+
68
+ ```bash
69
+ # npm
70
+ npm install @usefy/use-session-storage
71
+
72
+ # yarn
73
+ yarn add @usefy/use-session-storage
74
+
75
+ # pnpm
76
+ pnpm add @usefy/use-session-storage
77
+ ```
78
+
79
+ ### Peer Dependencies
80
+
81
+ This package requires React 18 or 19:
82
+
83
+ ```json
84
+ {
85
+ "peerDependencies": {
86
+ "react": "^18.0.0 || ^19.0.0"
87
+ }
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## Quick Start
94
+
95
+ ```tsx
96
+ import { useSessionStorage } from '@usefy/use-session-storage';
97
+
98
+ function CheckoutForm() {
99
+ const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {
100
+ name: '',
101
+ email: '',
102
+ address: '',
103
+ });
104
+
105
+ return (
106
+ <form>
107
+ <input
108
+ value={formData.name}
109
+ onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
110
+ placeholder="Name"
111
+ />
112
+ <input
113
+ value={formData.email}
114
+ onChange={(e) => setFormData((prev) => ({ ...prev, email: e.target.value }))}
115
+ placeholder="Email"
116
+ />
117
+ <button type="button" onClick={clearForm}>Clear Form</button>
118
+ </form>
119
+ );
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## API Reference
126
+
127
+ ### `useSessionStorage<T>(key, initialValue, options?)`
128
+
129
+ A hook that persists state in sessionStorage for the duration of the browser session.
130
+
131
+ #### Parameters
132
+
133
+ | Parameter | Type | Description |
134
+ |-----------|------|-------------|
135
+ | `key` | `string` | The sessionStorage key |
136
+ | `initialValue` | `T \| () => T` | Initial value or lazy initializer function |
137
+ | `options` | `UseSessionStorageOptions<T>` | Configuration options |
138
+
139
+ #### Options
140
+
141
+ | Option | Type | Default | Description |
142
+ |--------|------|---------|-------------|
143
+ | `serializer` | `(value: T) => string` | `JSON.stringify` | Custom serializer function |
144
+ | `deserializer` | `(value: string) => T` | `JSON.parse` | Custom deserializer function |
145
+ | `onError` | `(error: Error) => void` | — | Callback for error handling |
146
+
147
+ #### Returns `[T, SetValue<T>, RemoveValue]`
148
+
149
+ | Index | Type | Description |
150
+ |-------|------|-------------|
151
+ | `[0]` | `T` | Current stored value |
152
+ | `[1]` | `Dispatch<SetStateAction<T>>` | Function to update value (same as useState) |
153
+ | `[2]` | `() => void` | Function to remove value and reset to initial |
154
+
155
+ ---
156
+
157
+ ## Examples
158
+
159
+ ### Multi-Step Wizard
160
+
161
+ ```tsx
162
+ import { useSessionStorage } from '@usefy/use-session-storage';
163
+
164
+ function SignupWizard() {
165
+ const [step, setStep] = useSessionStorage('signup-step', 1);
166
+ const [formData, setFormData, resetForm] = useSessionStorage('signup-data', {
167
+ email: '',
168
+ password: '',
169
+ profile: {},
170
+ });
171
+
172
+ const handleNext = () => setStep((prev) => prev + 1);
173
+ const handleBack = () => setStep((prev) => prev - 1);
174
+
175
+ const handleComplete = async () => {
176
+ await submitSignup(formData);
177
+ resetForm();
178
+ setStep(1);
179
+ };
180
+
181
+ return (
182
+ <div>
183
+ <p>Step {step} of 3</p>
184
+
185
+ {step === 1 && (
186
+ <EmailStep
187
+ value={formData.email}
188
+ onChange={(email) => setFormData((prev) => ({ ...prev, email }))}
189
+ onNext={handleNext}
190
+ />
191
+ )}
192
+
193
+ {step === 2 && (
194
+ <PasswordStep
195
+ value={formData.password}
196
+ onChange={(password) => setFormData((prev) => ({ ...prev, password }))}
197
+ onBack={handleBack}
198
+ onNext={handleNext}
199
+ />
200
+ )}
201
+
202
+ {step === 3 && (
203
+ <ProfileStep
204
+ value={formData.profile}
205
+ onChange={(profile) => setFormData((prev) => ({ ...prev, profile }))}
206
+ onBack={handleBack}
207
+ onComplete={handleComplete}
208
+ />
209
+ )}
210
+ </div>
211
+ );
212
+ }
213
+ ```
214
+
215
+ ### Form Draft (Auto-Restore)
216
+
217
+ ```tsx
218
+ import { useSessionStorage } from '@usefy/use-session-storage';
219
+
220
+ function ContactForm() {
221
+ const [draft, setDraft, clearDraft] = useSessionStorage('contact-draft', {
222
+ subject: '',
223
+ message: '',
224
+ });
225
+
226
+ const handleSubmit = async (e: React.FormEvent) => {
227
+ e.preventDefault();
228
+ await sendMessage(draft);
229
+ clearDraft(); // Clear after successful submit
230
+ };
231
+
232
+ return (
233
+ <form onSubmit={handleSubmit}>
234
+ <input
235
+ value={draft.subject}
236
+ onChange={(e) => setDraft((prev) => ({ ...prev, subject: e.target.value }))}
237
+ placeholder="Subject"
238
+ />
239
+ <textarea
240
+ value={draft.message}
241
+ onChange={(e) => setDraft((prev) => ({ ...prev, message: e.target.value }))}
242
+ placeholder="Message"
243
+ />
244
+ <p className="hint">Your draft is auto-saved in this tab</p>
245
+ <button type="submit">Send</button>
246
+ <button type="button" onClick={clearDraft}>Discard</button>
247
+ </form>
248
+ );
249
+ }
250
+ ```
251
+
252
+ ### Shopping Cart (Per-Tab)
253
+
254
+ ```tsx
255
+ import { useSessionStorage } from '@usefy/use-session-storage';
256
+
257
+ interface CartItem {
258
+ id: string;
259
+ name: string;
260
+ quantity: number;
261
+ }
262
+
263
+ function TabCart() {
264
+ const [cart, setCart, clearCart] = useSessionStorage<CartItem[]>('tab-cart', []);
265
+
266
+ const addItem = (product: Product) => {
267
+ setCart((prev) => {
268
+ const existing = prev.find((item) => item.id === product.id);
269
+ if (existing) {
270
+ return prev.map((item) =>
271
+ item.id === product.id
272
+ ? { ...item, quantity: item.quantity + 1 }
273
+ : item
274
+ );
275
+ }
276
+ return [...prev, { id: product.id, name: product.name, quantity: 1 }];
277
+ });
278
+ };
279
+
280
+ return (
281
+ <div>
282
+ <p>Cart items: {cart.length}</p>
283
+ <p className="hint">This cart is specific to this tab only</p>
284
+ <button onClick={clearCart}>Clear Cart</button>
285
+ </div>
286
+ );
287
+ }
288
+ ```
289
+
290
+ ### Temporary Auth Token
291
+
292
+ ```tsx
293
+ import { useSessionStorage } from '@usefy/use-session-storage';
294
+
295
+ function ProtectedPage() {
296
+ const [token, setToken, clearToken] = useSessionStorage<string | null>('auth-token', null);
297
+
298
+ const login = async (credentials: Credentials) => {
299
+ const response = await authenticate(credentials);
300
+ setToken(response.token);
301
+ };
302
+
303
+ const logout = () => {
304
+ clearToken();
305
+ // Token is automatically cleared when tab closes
306
+ };
307
+
308
+ if (!token) {
309
+ return <LoginForm onLogin={login} />;
310
+ }
311
+
312
+ return (
313
+ <div>
314
+ <p>You are logged in (this session only)</p>
315
+ <button onClick={logout}>Logout</button>
316
+ </div>
317
+ );
318
+ }
319
+ ```
320
+
321
+ ### Custom Serialization (Date)
322
+
323
+ ```tsx
324
+ import { useSessionStorage } from '@usefy/use-session-storage';
325
+
326
+ function SessionTimer() {
327
+ const [sessionStart] = useSessionStorage<Date>(
328
+ 'session-start',
329
+ new Date(),
330
+ {
331
+ serializer: (date) => date.toISOString(),
332
+ deserializer: (str) => new Date(str),
333
+ }
334
+ );
335
+
336
+ const [elapsed, setElapsed] = useState(0);
337
+
338
+ useEffect(() => {
339
+ const interval = setInterval(() => {
340
+ setElapsed(Math.floor((Date.now() - sessionStart.getTime()) / 1000));
341
+ }, 1000);
342
+ return () => clearInterval(interval);
343
+ }, [sessionStart]);
344
+
345
+ return <div>Session duration: {elapsed} seconds</div>;
346
+ }
347
+ ```
348
+
349
+ ### Error Handling
350
+
351
+ ```tsx
352
+ import { useSessionStorage } from '@usefy/use-session-storage';
353
+
354
+ function RobustSessionStorage() {
355
+ const [data, setData] = useSessionStorage('session-data', { items: [] }, {
356
+ onError: (error) => {
357
+ console.error('Session storage error:', error.message);
358
+ toast.error('Failed to save session data');
359
+ },
360
+ });
361
+
362
+ return <DataEditor data={data} onChange={setData} />;
363
+ }
364
+ ```
365
+
366
+ ### Lazy Initialization
367
+
368
+ ```tsx
369
+ import { useSessionStorage } from '@usefy/use-session-storage';
370
+
371
+ function ExpensiveDefaultDemo() {
372
+ // Expensive computation only runs if no stored value exists
373
+ const [cache, setCache] = useSessionStorage('session-cache', () => {
374
+ console.log('Building initial cache...');
375
+ return buildExpensiveCache();
376
+ });
377
+
378
+ return <CacheViewer cache={cache} />;
379
+ }
380
+ ```
381
+
382
+ ### Quiz Progress
383
+
384
+ ```tsx
385
+ import { useSessionStorage } from '@usefy/use-session-storage';
386
+
387
+ interface QuizState {
388
+ currentQuestion: number;
389
+ answers: Record<number, string>;
390
+ startTime: number;
391
+ }
392
+
393
+ function Quiz() {
394
+ const [quiz, setQuiz, resetQuiz] = useSessionStorage<QuizState>('quiz-progress', {
395
+ currentQuestion: 0,
396
+ answers: {},
397
+ startTime: Date.now(),
398
+ });
399
+
400
+ const submitAnswer = (answer: string) => {
401
+ setQuiz((prev) => ({
402
+ ...prev,
403
+ answers: { ...prev.answers, [prev.currentQuestion]: answer },
404
+ currentQuestion: prev.currentQuestion + 1,
405
+ }));
406
+ };
407
+
408
+ const handleComplete = async () => {
409
+ await submitQuiz(quiz.answers);
410
+ resetQuiz();
411
+ };
412
+
413
+ return (
414
+ <div>
415
+ <p>Question {quiz.currentQuestion + 1} of 10</p>
416
+ <QuestionCard
417
+ question={questions[quiz.currentQuestion]}
418
+ onAnswer={submitAnswer}
419
+ />
420
+ <button onClick={resetQuiz}>Restart Quiz</button>
421
+ </div>
422
+ );
423
+ }
424
+ ```
425
+
426
+ ---
427
+
428
+ ## TypeScript
429
+
430
+ This hook is written in TypeScript with full generic support.
431
+
432
+ ```tsx
433
+ import {
434
+ useSessionStorage,
435
+ type UseSessionStorageOptions,
436
+ type UseSessionStorageReturn,
437
+ type InitialValue,
438
+ } from '@usefy/use-session-storage';
439
+
440
+ // Generic type inference
441
+ const [name, setName] = useSessionStorage('name', 'Guest'); // string
442
+ const [step, setStep] = useSessionStorage('step', 1); // number
443
+ const [items, setItems] = useSessionStorage('items', ['a']); // string[]
444
+
445
+ // Explicit generic type
446
+ interface FormData {
447
+ email: string;
448
+ message: string;
449
+ }
450
+ const [form, setForm] = useSessionStorage<FormData>('form', {
451
+ email: '',
452
+ message: '',
453
+ });
454
+ ```
455
+
456
+ ---
457
+
458
+ ## Testing
459
+
460
+ This package maintains comprehensive test coverage to ensure reliability and stability.
461
+
462
+ ### Test Coverage
463
+
464
+ | Category | Tests | Coverage |
465
+ |----------|-------|----------|
466
+ | Initialization | 6 | 100% |
467
+ | setValue | 5 | 100% |
468
+ | removeValue | 2 | 100% |
469
+ | Type Preservation | 5 | 100% |
470
+ | Custom Serialization | 2 | 100% |
471
+ | Key Changes | 2 | 100% |
472
+ | Function Stability | 3 | 100% |
473
+ | Multiple Instances | 1 | 100% |
474
+ | Edge Cases | 6 | 100% |
475
+ | **Total** | **32** | **93.75%** |
476
+
477
+ ### Test Categories
478
+
479
+ <details>
480
+ <summary><strong>Initialization Tests</strong></summary>
481
+
482
+ - Return initial value when sessionStorage is empty
483
+ - Return stored value when sessionStorage has data
484
+ - Support lazy initialization with function
485
+ - Not call initializer when sessionStorage has data
486
+ - Fallback to initial value when JSON parse fails
487
+ - Call onError when sessionStorage read fails
488
+
489
+ </details>
490
+
491
+ <details>
492
+ <summary><strong>setValue Tests</strong></summary>
493
+
494
+ - Update value and sessionStorage
495
+ - Support functional updates
496
+ - Handle object values
497
+ - Handle array values
498
+ - Call onError when write fails
499
+
500
+ </details>
501
+
502
+ ### Running Tests
503
+
504
+ ```bash
505
+ # Run all tests
506
+ pnpm test
507
+
508
+ # Run tests in watch mode
509
+ pnpm test:watch
510
+
511
+ # Run tests with coverage report
512
+ pnpm test --coverage
513
+ ```
514
+
515
+ ---
516
+
517
+ ## Related Packages
518
+
519
+ Explore other hooks in the **@usefy** collection:
520
+
521
+ | Package | Description |
522
+ |---------|-------------|
523
+ | [@usefy/use-local-storage](https://www.npmjs.com/package/@usefy/use-local-storage) | Persistent localStorage |
524
+ | [@usefy/use-toggle](https://www.npmjs.com/package/@usefy/use-toggle) | Boolean state management |
525
+ | [@usefy/use-counter](https://www.npmjs.com/package/@usefy/use-counter) | Counter state management |
526
+ | [@usefy/use-debounce](https://www.npmjs.com/package/@usefy/use-debounce) | Value debouncing |
527
+ | [@usefy/use-throttle](https://www.npmjs.com/package/@usefy/use-throttle) | Value throttling |
528
+ | [@usefy/use-click-any-where](https://www.npmjs.com/package/@usefy/use-click-any-where) | Global click detection |
529
+
530
+ ---
531
+
532
+ ## Contributing
533
+
534
+ We welcome contributions! Please see our [Contributing Guide](https://github.com/geon0529/usefy/blob/master/CONTRIBUTING.md) for details.
535
+
536
+ ```bash
537
+ # Clone the repository
538
+ git clone https://github.com/geon0529/usefy.git
539
+
540
+ # Install dependencies
541
+ pnpm install
542
+
543
+ # Run tests
544
+ pnpm test
545
+
546
+ # Build
547
+ pnpm build
548
+ ```
549
+
550
+ ---
551
+
552
+ ## License
553
+
554
+ MIT © [mirunamu](https://github.com/geon0529)
555
+
556
+ This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
557
+
558
+ ---
559
+
560
+ <p align="center">
561
+ <sub>Built with care by the usefy team</sub>
562
+ </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-session-storage",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "A React hook for persisting state in sessionStorage",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",