@usefy/use-session-storage 0.0.7 → 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.
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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useSessionStorage.ts"],"sourcesContent":["export {\r\n useSessionStorage,\r\n type UseSessionStorageOptions,\r\n type UseSessionStorageReturn,\r\n type InitialValue,\r\n} from \"./useSessionStorage\";\r\n","import { useCallback, useEffect, useRef, useState } from \"react\";\r\n\r\n/**\r\n * Type for initial value that can be a value or a function returning a value (lazy initialization)\r\n */\r\nexport type InitialValue<T> = T | (() => T);\r\n\r\n/**\r\n * Options for useSessionStorage hook\r\n */\r\nexport interface UseSessionStorageOptions<T> {\r\n /**\r\n * Custom serializer function for converting value to string\r\n * @default JSON.stringify\r\n */\r\n serializer?: (value: T) => string;\r\n /**\r\n * Custom deserializer function for parsing stored string to value\r\n * @default JSON.parse\r\n */\r\n deserializer?: (value: string) => T;\r\n /**\r\n * Callback function called when an error occurs\r\n */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Return type for useSessionStorage hook - tuple similar to useState\r\n */\r\nexport type UseSessionStorageReturn<T> = readonly [\r\n /** Current stored value */\r\n T,\r\n /** Function to update the value (same signature as useState setter) */\r\n React.Dispatch<React.SetStateAction<T>>,\r\n /** Function to remove the value from sessionStorage */\r\n () => void\r\n];\r\n\r\n/**\r\n * Helper function to resolve initial value (supports lazy initialization)\r\n */\r\nfunction resolveInitialValue<T>(initialValue: InitialValue<T>): T {\r\n return typeof initialValue === \"function\"\r\n ? (initialValue as () => T)()\r\n : initialValue;\r\n}\r\n\r\n/**\r\n * A hook for persisting state in sessionStorage.\r\n * Works like useState but persists the value in sessionStorage for the duration of the browser session.\r\n *\r\n * Unlike localStorage, sessionStorage data:\r\n * - Is cleared when the tab/window is closed\r\n * - Is not shared between tabs (each tab has its own session)\r\n *\r\n * @template T - The type of the stored value\r\n * @param key - The sessionStorage key to store the value under\r\n * @param initialValue - Initial value or function returning initial value (lazy initialization)\r\n * @param options - Configuration options for serialization and error handling\r\n * @returns A tuple of [storedValue, setValue, removeValue]\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage - form data that persists during session\r\n * function CheckoutForm() {\r\n * const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {\r\n * name: '',\r\n * email: '',\r\n * });\r\n *\r\n * return (\r\n * <form>\r\n * <input\r\n * value={formData.name}\r\n * onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}\r\n * />\r\n * <button type=\"button\" onClick={clearForm}>Clear</button>\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // Temporary state that resets on tab close\r\n * const [wizardStep, setWizardStep] = useSessionStorage('wizard-step', 1);\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With lazy initialization\r\n * const [cache, setCache] = useSessionStorage('cache', () => computeInitialCache());\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With custom serializer/deserializer\r\n * const [date, setDate] = useSessionStorage<Date>('lastAction', new Date(), {\r\n * serializer: (d) => d.toISOString(),\r\n * deserializer: (s) => new Date(s),\r\n * });\r\n * ```\r\n */\r\nexport function useSessionStorage<T>(\r\n key: string,\r\n initialValue: InitialValue<T>,\r\n options: UseSessionStorageOptions<T> = {}\r\n): UseSessionStorageReturn<T> {\r\n const {\r\n serializer = JSON.stringify,\r\n deserializer = JSON.parse,\r\n onError,\r\n } = options;\r\n\r\n // Store options in refs for stable references and access to latest values\r\n const serializerRef = useRef(serializer);\r\n const deserializerRef = useRef(deserializer);\r\n const onErrorRef = useRef(onError);\r\n serializerRef.current = serializer;\r\n deserializerRef.current = deserializer;\r\n onErrorRef.current = onError;\r\n\r\n // Store initialValue in ref for use in removeValue\r\n const initialValueRef = useRef(initialValue);\r\n initialValueRef.current = initialValue;\r\n\r\n // SSR check\r\n const isClient = typeof window !== \"undefined\";\r\n\r\n // Lazy initialization with sessionStorage read\r\n const [storedValue, setStoredValue] = useState<T>(() => {\r\n if (!isClient) {\r\n return resolveInitialValue(initialValue);\r\n }\r\n\r\n try {\r\n const item = window.sessionStorage.getItem(key);\r\n if (item !== null) {\r\n return deserializerRef.current(item);\r\n }\r\n return resolveInitialValue(initialValue);\r\n } catch (error) {\r\n onErrorRef.current?.(error as Error);\r\n return resolveInitialValue(initialValue);\r\n }\r\n });\r\n\r\n // Store current value in ref for stable setValue reference\r\n const storedValueRef = useRef<T>(storedValue);\r\n storedValueRef.current = storedValue;\r\n\r\n // setValue - stable reference (only depends on key)\r\n const setValue = useCallback<React.Dispatch<React.SetStateAction<T>>>(\r\n (value) => {\r\n try {\r\n const currentValue = storedValueRef.current;\r\n const valueToStore =\r\n value instanceof Function ? value(currentValue) : value;\r\n\r\n setStoredValue(valueToStore);\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.sessionStorage.setItem(\r\n key,\r\n serializerRef.current(valueToStore)\r\n );\r\n }\r\n } catch (error) {\r\n onErrorRef.current?.(error as Error);\r\n }\r\n },\r\n [key]\r\n );\r\n\r\n // removeValue - stable reference\r\n const removeValue = useCallback(() => {\r\n try {\r\n const initial = resolveInitialValue(initialValueRef.current);\r\n setStoredValue(initial);\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.sessionStorage.removeItem(key);\r\n }\r\n } catch (error) {\r\n onErrorRef.current?.(error as Error);\r\n }\r\n }, [key]);\r\n\r\n // Re-read value when key changes\r\n useEffect(() => {\r\n if (!isClient) {\r\n return;\r\n }\r\n\r\n try {\r\n const item = window.sessionStorage.getItem(key);\r\n if (item !== null) {\r\n setStoredValue(deserializerRef.current(item));\r\n } else {\r\n setStoredValue(resolveInitialValue(initialValueRef.current));\r\n }\r\n } catch {\r\n setStoredValue(resolveInitialValue(initialValueRef.current));\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [key]);\r\n\r\n return [storedValue, setValue, removeValue] as const;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAyD;AA0CzD,SAAS,oBAAuB,cAAkC;AAChE,SAAO,OAAO,iBAAiB,aAC1B,aAAyB,IAC1B;AACN;AA0DO,SAAS,kBACd,KACA,cACA,UAAuC,CAAC,GACZ;AAC5B,QAAM;AAAA,IACJ,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK;AAAA,IACpB;AAAA,EACF,IAAI;AAGJ,QAAM,oBAAgB,qBAAO,UAAU;AACvC,QAAM,sBAAkB,qBAAO,YAAY;AAC3C,QAAM,iBAAa,qBAAO,OAAO;AACjC,gBAAc,UAAU;AACxB,kBAAgB,UAAU;AAC1B,aAAW,UAAU;AAGrB,QAAM,sBAAkB,qBAAO,YAAY;AAC3C,kBAAgB,UAAU;AAG1B,QAAM,WAAW,OAAO,WAAW;AAGnC,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAY,MAAM;AACtD,QAAI,CAAC,UAAU;AACb,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,eAAO,gBAAgB,QAAQ,IAAI;AAAA,MACrC;AACA,aAAO,oBAAoB,YAAY;AAAA,IACzC,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AACnC,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAAA,EACF,CAAC;AAGD,QAAM,qBAAiB,qBAAU,WAAW;AAC5C,iBAAe,UAAU;AAGzB,QAAM,eAAW;AAAA,IACf,CAAC,UAAU;AACT,UAAI;AACF,cAAM,eAAe,eAAe;AACpC,cAAM,eACJ,iBAAiB,WAAW,MAAM,YAAY,IAAI;AAEpD,uBAAe,YAAY;AAE3B,YAAI,OAAO,WAAW,aAAa;AACjC,iBAAO,eAAe;AAAA,YACpB;AAAA,YACA,cAAc,QAAQ,YAAY;AAAA,UACpC;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,mBAAW,UAAU,KAAc;AAAA,MACrC;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAGA,QAAM,kBAAc,0BAAY,MAAM;AACpC,QAAI;AACF,YAAM,UAAU,oBAAoB,gBAAgB,OAAO;AAC3D,qBAAe,OAAO;AAEtB,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,eAAe,WAAW,GAAG;AAAA,MACtC;AAAA,IACF,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AAGR,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,uBAAe,gBAAgB,QAAQ,IAAI,CAAC;AAAA,MAC9C,OAAO;AACL,uBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,MAC7D;AAAA,IACF,QAAQ;AACN,qBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,IAC7D;AAAA,EAEF,GAAG,CAAC,GAAG,CAAC;AAER,SAAO,CAAC,aAAa,UAAU,WAAW;AAC5C;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useSessionStorage.ts"],"sourcesContent":["export {\n useSessionStorage,\n type UseSessionStorageOptions,\n type UseSessionStorageReturn,\n type InitialValue,\n} from \"./useSessionStorage\";\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Type for initial value that can be a value or a function returning a value (lazy initialization)\n */\nexport type InitialValue<T> = T | (() => T);\n\n/**\n * Options for useSessionStorage hook\n */\nexport interface UseSessionStorageOptions<T> {\n /**\n * Custom serializer function for converting value to string\n * @default JSON.stringify\n */\n serializer?: (value: T) => string;\n /**\n * Custom deserializer function for parsing stored string to value\n * @default JSON.parse\n */\n deserializer?: (value: string) => T;\n /**\n * Callback function called when an error occurs\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Return type for useSessionStorage hook - tuple similar to useState\n */\nexport type UseSessionStorageReturn<T> = readonly [\n /** Current stored value */\n T,\n /** Function to update the value (same signature as useState setter) */\n React.Dispatch<React.SetStateAction<T>>,\n /** Function to remove the value from sessionStorage */\n () => void\n];\n\n/**\n * Helper function to resolve initial value (supports lazy initialization)\n */\nfunction resolveInitialValue<T>(initialValue: InitialValue<T>): T {\n return typeof initialValue === \"function\"\n ? (initialValue as () => T)()\n : initialValue;\n}\n\n/**\n * A hook for persisting state in sessionStorage.\n * Works like useState but persists the value in sessionStorage for the duration of the browser session.\n *\n * Unlike localStorage, sessionStorage data:\n * - Is cleared when the tab/window is closed\n * - Is not shared between tabs (each tab has its own session)\n *\n * @template T - The type of the stored value\n * @param key - The sessionStorage key to store the value under\n * @param initialValue - Initial value or function returning initial value (lazy initialization)\n * @param options - Configuration options for serialization and error handling\n * @returns A tuple of [storedValue, setValue, removeValue]\n *\n * @example\n * ```tsx\n * // Basic usage - form data that persists during session\n * function CheckoutForm() {\n * const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {\n * name: '',\n * email: '',\n * });\n *\n * return (\n * <form>\n * <input\n * value={formData.name}\n * onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}\n * />\n * <button type=\"button\" onClick={clearForm}>Clear</button>\n * </form>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Temporary state that resets on tab close\n * const [wizardStep, setWizardStep] = useSessionStorage('wizard-step', 1);\n * ```\n *\n * @example\n * ```tsx\n * // With lazy initialization\n * const [cache, setCache] = useSessionStorage('cache', () => computeInitialCache());\n * ```\n *\n * @example\n * ```tsx\n * // With custom serializer/deserializer\n * const [date, setDate] = useSessionStorage<Date>('lastAction', new Date(), {\n * serializer: (d) => d.toISOString(),\n * deserializer: (s) => new Date(s),\n * });\n * ```\n */\nexport function useSessionStorage<T>(\n key: string,\n initialValue: InitialValue<T>,\n options: UseSessionStorageOptions<T> = {}\n): UseSessionStorageReturn<T> {\n const {\n serializer = JSON.stringify,\n deserializer = JSON.parse,\n onError,\n } = options;\n\n // Store options in refs for stable references and access to latest values\n const serializerRef = useRef(serializer);\n const deserializerRef = useRef(deserializer);\n const onErrorRef = useRef(onError);\n serializerRef.current = serializer;\n deserializerRef.current = deserializer;\n onErrorRef.current = onError;\n\n // Store initialValue in ref for use in removeValue\n const initialValueRef = useRef(initialValue);\n initialValueRef.current = initialValue;\n\n // SSR check\n const isClient = typeof window !== \"undefined\";\n\n // Lazy initialization with sessionStorage read\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (!isClient) {\n return resolveInitialValue(initialValue);\n }\n\n try {\n const item = window.sessionStorage.getItem(key);\n if (item !== null) {\n return deserializerRef.current(item);\n }\n return resolveInitialValue(initialValue);\n } catch (error) {\n onErrorRef.current?.(error as Error);\n return resolveInitialValue(initialValue);\n }\n });\n\n // Store current value in ref for stable setValue reference\n const storedValueRef = useRef<T>(storedValue);\n storedValueRef.current = storedValue;\n\n // setValue - stable reference (only depends on key)\n const setValue = useCallback<React.Dispatch<React.SetStateAction<T>>>(\n (value) => {\n try {\n const currentValue = storedValueRef.current;\n const valueToStore =\n value instanceof Function ? value(currentValue) : value;\n\n setStoredValue(valueToStore);\n\n if (typeof window !== \"undefined\") {\n window.sessionStorage.setItem(\n key,\n serializerRef.current(valueToStore)\n );\n }\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n },\n [key]\n );\n\n // removeValue - stable reference\n const removeValue = useCallback(() => {\n try {\n const initial = resolveInitialValue(initialValueRef.current);\n setStoredValue(initial);\n\n if (typeof window !== \"undefined\") {\n window.sessionStorage.removeItem(key);\n }\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n }, [key]);\n\n // Re-read value when key changes\n useEffect(() => {\n if (!isClient) {\n return;\n }\n\n try {\n const item = window.sessionStorage.getItem(key);\n if (item !== null) {\n setStoredValue(deserializerRef.current(item));\n } else {\n setStoredValue(resolveInitialValue(initialValueRef.current));\n }\n } catch {\n setStoredValue(resolveInitialValue(initialValueRef.current));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key]);\n\n return [storedValue, setValue, removeValue] as const;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAyD;AA0CzD,SAAS,oBAAuB,cAAkC;AAChE,SAAO,OAAO,iBAAiB,aAC1B,aAAyB,IAC1B;AACN;AA0DO,SAAS,kBACd,KACA,cACA,UAAuC,CAAC,GACZ;AAC5B,QAAM;AAAA,IACJ,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK;AAAA,IACpB;AAAA,EACF,IAAI;AAGJ,QAAM,oBAAgB,qBAAO,UAAU;AACvC,QAAM,sBAAkB,qBAAO,YAAY;AAC3C,QAAM,iBAAa,qBAAO,OAAO;AACjC,gBAAc,UAAU;AACxB,kBAAgB,UAAU;AAC1B,aAAW,UAAU;AAGrB,QAAM,sBAAkB,qBAAO,YAAY;AAC3C,kBAAgB,UAAU;AAG1B,QAAM,WAAW,OAAO,WAAW;AAGnC,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAY,MAAM;AACtD,QAAI,CAAC,UAAU;AACb,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,eAAO,gBAAgB,QAAQ,IAAI;AAAA,MACrC;AACA,aAAO,oBAAoB,YAAY;AAAA,IACzC,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AACnC,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAAA,EACF,CAAC;AAGD,QAAM,qBAAiB,qBAAU,WAAW;AAC5C,iBAAe,UAAU;AAGzB,QAAM,eAAW;AAAA,IACf,CAAC,UAAU;AACT,UAAI;AACF,cAAM,eAAe,eAAe;AACpC,cAAM,eACJ,iBAAiB,WAAW,MAAM,YAAY,IAAI;AAEpD,uBAAe,YAAY;AAE3B,YAAI,OAAO,WAAW,aAAa;AACjC,iBAAO,eAAe;AAAA,YACpB;AAAA,YACA,cAAc,QAAQ,YAAY;AAAA,UACpC;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,mBAAW,UAAU,KAAc;AAAA,MACrC;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAGA,QAAM,kBAAc,0BAAY,MAAM;AACpC,QAAI;AACF,YAAM,UAAU,oBAAoB,gBAAgB,OAAO;AAC3D,qBAAe,OAAO;AAEtB,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,eAAe,WAAW,GAAG;AAAA,MACtC;AAAA,IACF,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AAGR,8BAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,uBAAe,gBAAgB,QAAQ,IAAI,CAAC;AAAA,MAC9C,OAAO;AACL,uBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,MAC7D;AAAA,IACF,QAAQ;AACN,qBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,IAC7D;AAAA,EAEF,GAAG,CAAC,GAAG,CAAC;AAER,SAAO,CAAC,aAAa,UAAU,WAAW;AAC5C;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useSessionStorage.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\r\n\r\n/**\r\n * Type for initial value that can be a value or a function returning a value (lazy initialization)\r\n */\r\nexport type InitialValue<T> = T | (() => T);\r\n\r\n/**\r\n * Options for useSessionStorage hook\r\n */\r\nexport interface UseSessionStorageOptions<T> {\r\n /**\r\n * Custom serializer function for converting value to string\r\n * @default JSON.stringify\r\n */\r\n serializer?: (value: T) => string;\r\n /**\r\n * Custom deserializer function for parsing stored string to value\r\n * @default JSON.parse\r\n */\r\n deserializer?: (value: string) => T;\r\n /**\r\n * Callback function called when an error occurs\r\n */\r\n onError?: (error: Error) => void;\r\n}\r\n\r\n/**\r\n * Return type for useSessionStorage hook - tuple similar to useState\r\n */\r\nexport type UseSessionStorageReturn<T> = readonly [\r\n /** Current stored value */\r\n T,\r\n /** Function to update the value (same signature as useState setter) */\r\n React.Dispatch<React.SetStateAction<T>>,\r\n /** Function to remove the value from sessionStorage */\r\n () => void\r\n];\r\n\r\n/**\r\n * Helper function to resolve initial value (supports lazy initialization)\r\n */\r\nfunction resolveInitialValue<T>(initialValue: InitialValue<T>): T {\r\n return typeof initialValue === \"function\"\r\n ? (initialValue as () => T)()\r\n : initialValue;\r\n}\r\n\r\n/**\r\n * A hook for persisting state in sessionStorage.\r\n * Works like useState but persists the value in sessionStorage for the duration of the browser session.\r\n *\r\n * Unlike localStorage, sessionStorage data:\r\n * - Is cleared when the tab/window is closed\r\n * - Is not shared between tabs (each tab has its own session)\r\n *\r\n * @template T - The type of the stored value\r\n * @param key - The sessionStorage key to store the value under\r\n * @param initialValue - Initial value or function returning initial value (lazy initialization)\r\n * @param options - Configuration options for serialization and error handling\r\n * @returns A tuple of [storedValue, setValue, removeValue]\r\n *\r\n * @example\r\n * ```tsx\r\n * // Basic usage - form data that persists during session\r\n * function CheckoutForm() {\r\n * const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {\r\n * name: '',\r\n * email: '',\r\n * });\r\n *\r\n * return (\r\n * <form>\r\n * <input\r\n * value={formData.name}\r\n * onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}\r\n * />\r\n * <button type=\"button\" onClick={clearForm}>Clear</button>\r\n * </form>\r\n * );\r\n * }\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // Temporary state that resets on tab close\r\n * const [wizardStep, setWizardStep] = useSessionStorage('wizard-step', 1);\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With lazy initialization\r\n * const [cache, setCache] = useSessionStorage('cache', () => computeInitialCache());\r\n * ```\r\n *\r\n * @example\r\n * ```tsx\r\n * // With custom serializer/deserializer\r\n * const [date, setDate] = useSessionStorage<Date>('lastAction', new Date(), {\r\n * serializer: (d) => d.toISOString(),\r\n * deserializer: (s) => new Date(s),\r\n * });\r\n * ```\r\n */\r\nexport function useSessionStorage<T>(\r\n key: string,\r\n initialValue: InitialValue<T>,\r\n options: UseSessionStorageOptions<T> = {}\r\n): UseSessionStorageReturn<T> {\r\n const {\r\n serializer = JSON.stringify,\r\n deserializer = JSON.parse,\r\n onError,\r\n } = options;\r\n\r\n // Store options in refs for stable references and access to latest values\r\n const serializerRef = useRef(serializer);\r\n const deserializerRef = useRef(deserializer);\r\n const onErrorRef = useRef(onError);\r\n serializerRef.current = serializer;\r\n deserializerRef.current = deserializer;\r\n onErrorRef.current = onError;\r\n\r\n // Store initialValue in ref for use in removeValue\r\n const initialValueRef = useRef(initialValue);\r\n initialValueRef.current = initialValue;\r\n\r\n // SSR check\r\n const isClient = typeof window !== \"undefined\";\r\n\r\n // Lazy initialization with sessionStorage read\r\n const [storedValue, setStoredValue] = useState<T>(() => {\r\n if (!isClient) {\r\n return resolveInitialValue(initialValue);\r\n }\r\n\r\n try {\r\n const item = window.sessionStorage.getItem(key);\r\n if (item !== null) {\r\n return deserializerRef.current(item);\r\n }\r\n return resolveInitialValue(initialValue);\r\n } catch (error) {\r\n onErrorRef.current?.(error as Error);\r\n return resolveInitialValue(initialValue);\r\n }\r\n });\r\n\r\n // Store current value in ref for stable setValue reference\r\n const storedValueRef = useRef<T>(storedValue);\r\n storedValueRef.current = storedValue;\r\n\r\n // setValue - stable reference (only depends on key)\r\n const setValue = useCallback<React.Dispatch<React.SetStateAction<T>>>(\r\n (value) => {\r\n try {\r\n const currentValue = storedValueRef.current;\r\n const valueToStore =\r\n value instanceof Function ? value(currentValue) : value;\r\n\r\n setStoredValue(valueToStore);\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.sessionStorage.setItem(\r\n key,\r\n serializerRef.current(valueToStore)\r\n );\r\n }\r\n } catch (error) {\r\n onErrorRef.current?.(error as Error);\r\n }\r\n },\r\n [key]\r\n );\r\n\r\n // removeValue - stable reference\r\n const removeValue = useCallback(() => {\r\n try {\r\n const initial = resolveInitialValue(initialValueRef.current);\r\n setStoredValue(initial);\r\n\r\n if (typeof window !== \"undefined\") {\r\n window.sessionStorage.removeItem(key);\r\n }\r\n } catch (error) {\r\n onErrorRef.current?.(error as Error);\r\n }\r\n }, [key]);\r\n\r\n // Re-read value when key changes\r\n useEffect(() => {\r\n if (!isClient) {\r\n return;\r\n }\r\n\r\n try {\r\n const item = window.sessionStorage.getItem(key);\r\n if (item !== null) {\r\n setStoredValue(deserializerRef.current(item));\r\n } else {\r\n setStoredValue(resolveInitialValue(initialValueRef.current));\r\n }\r\n } catch {\r\n setStoredValue(resolveInitialValue(initialValueRef.current));\r\n }\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [key]);\r\n\r\n return [storedValue, setValue, removeValue] as const;\r\n}\r\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AA0CzD,SAAS,oBAAuB,cAAkC;AAChE,SAAO,OAAO,iBAAiB,aAC1B,aAAyB,IAC1B;AACN;AA0DO,SAAS,kBACd,KACA,cACA,UAAuC,CAAC,GACZ;AAC5B,QAAM;AAAA,IACJ,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK;AAAA,IACpB;AAAA,EACF,IAAI;AAGJ,QAAM,gBAAgB,OAAO,UAAU;AACvC,QAAM,kBAAkB,OAAO,YAAY;AAC3C,QAAM,aAAa,OAAO,OAAO;AACjC,gBAAc,UAAU;AACxB,kBAAgB,UAAU;AAC1B,aAAW,UAAU;AAGrB,QAAM,kBAAkB,OAAO,YAAY;AAC3C,kBAAgB,UAAU;AAG1B,QAAM,WAAW,OAAO,WAAW;AAGnC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAY,MAAM;AACtD,QAAI,CAAC,UAAU;AACb,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,eAAO,gBAAgB,QAAQ,IAAI;AAAA,MACrC;AACA,aAAO,oBAAoB,YAAY;AAAA,IACzC,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AACnC,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAAA,EACF,CAAC;AAGD,QAAM,iBAAiB,OAAU,WAAW;AAC5C,iBAAe,UAAU;AAGzB,QAAM,WAAW;AAAA,IACf,CAAC,UAAU;AACT,UAAI;AACF,cAAM,eAAe,eAAe;AACpC,cAAM,eACJ,iBAAiB,WAAW,MAAM,YAAY,IAAI;AAEpD,uBAAe,YAAY;AAE3B,YAAI,OAAO,WAAW,aAAa;AACjC,iBAAO,eAAe;AAAA,YACpB;AAAA,YACA,cAAc,QAAQ,YAAY;AAAA,UACpC;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,mBAAW,UAAU,KAAc;AAAA,MACrC;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAGA,QAAM,cAAc,YAAY,MAAM;AACpC,QAAI;AACF,YAAM,UAAU,oBAAoB,gBAAgB,OAAO;AAC3D,qBAAe,OAAO;AAEtB,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,eAAe,WAAW,GAAG;AAAA,MACtC;AAAA,IACF,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AAGR,YAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,uBAAe,gBAAgB,QAAQ,IAAI,CAAC;AAAA,MAC9C,OAAO;AACL,uBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,MAC7D;AAAA,IACF,QAAQ;AACN,qBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,IAC7D;AAAA,EAEF,GAAG,CAAC,GAAG,CAAC;AAER,SAAO,CAAC,aAAa,UAAU,WAAW;AAC5C;","names":[]}
1
+ {"version":3,"sources":["../src/useSessionStorage.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/**\n * Type for initial value that can be a value or a function returning a value (lazy initialization)\n */\nexport type InitialValue<T> = T | (() => T);\n\n/**\n * Options for useSessionStorage hook\n */\nexport interface UseSessionStorageOptions<T> {\n /**\n * Custom serializer function for converting value to string\n * @default JSON.stringify\n */\n serializer?: (value: T) => string;\n /**\n * Custom deserializer function for parsing stored string to value\n * @default JSON.parse\n */\n deserializer?: (value: string) => T;\n /**\n * Callback function called when an error occurs\n */\n onError?: (error: Error) => void;\n}\n\n/**\n * Return type for useSessionStorage hook - tuple similar to useState\n */\nexport type UseSessionStorageReturn<T> = readonly [\n /** Current stored value */\n T,\n /** Function to update the value (same signature as useState setter) */\n React.Dispatch<React.SetStateAction<T>>,\n /** Function to remove the value from sessionStorage */\n () => void\n];\n\n/**\n * Helper function to resolve initial value (supports lazy initialization)\n */\nfunction resolveInitialValue<T>(initialValue: InitialValue<T>): T {\n return typeof initialValue === \"function\"\n ? (initialValue as () => T)()\n : initialValue;\n}\n\n/**\n * A hook for persisting state in sessionStorage.\n * Works like useState but persists the value in sessionStorage for the duration of the browser session.\n *\n * Unlike localStorage, sessionStorage data:\n * - Is cleared when the tab/window is closed\n * - Is not shared between tabs (each tab has its own session)\n *\n * @template T - The type of the stored value\n * @param key - The sessionStorage key to store the value under\n * @param initialValue - Initial value or function returning initial value (lazy initialization)\n * @param options - Configuration options for serialization and error handling\n * @returns A tuple of [storedValue, setValue, removeValue]\n *\n * @example\n * ```tsx\n * // Basic usage - form data that persists during session\n * function CheckoutForm() {\n * const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {\n * name: '',\n * email: '',\n * });\n *\n * return (\n * <form>\n * <input\n * value={formData.name}\n * onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}\n * />\n * <button type=\"button\" onClick={clearForm}>Clear</button>\n * </form>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Temporary state that resets on tab close\n * const [wizardStep, setWizardStep] = useSessionStorage('wizard-step', 1);\n * ```\n *\n * @example\n * ```tsx\n * // With lazy initialization\n * const [cache, setCache] = useSessionStorage('cache', () => computeInitialCache());\n * ```\n *\n * @example\n * ```tsx\n * // With custom serializer/deserializer\n * const [date, setDate] = useSessionStorage<Date>('lastAction', new Date(), {\n * serializer: (d) => d.toISOString(),\n * deserializer: (s) => new Date(s),\n * });\n * ```\n */\nexport function useSessionStorage<T>(\n key: string,\n initialValue: InitialValue<T>,\n options: UseSessionStorageOptions<T> = {}\n): UseSessionStorageReturn<T> {\n const {\n serializer = JSON.stringify,\n deserializer = JSON.parse,\n onError,\n } = options;\n\n // Store options in refs for stable references and access to latest values\n const serializerRef = useRef(serializer);\n const deserializerRef = useRef(deserializer);\n const onErrorRef = useRef(onError);\n serializerRef.current = serializer;\n deserializerRef.current = deserializer;\n onErrorRef.current = onError;\n\n // Store initialValue in ref for use in removeValue\n const initialValueRef = useRef(initialValue);\n initialValueRef.current = initialValue;\n\n // SSR check\n const isClient = typeof window !== \"undefined\";\n\n // Lazy initialization with sessionStorage read\n const [storedValue, setStoredValue] = useState<T>(() => {\n if (!isClient) {\n return resolveInitialValue(initialValue);\n }\n\n try {\n const item = window.sessionStorage.getItem(key);\n if (item !== null) {\n return deserializerRef.current(item);\n }\n return resolveInitialValue(initialValue);\n } catch (error) {\n onErrorRef.current?.(error as Error);\n return resolveInitialValue(initialValue);\n }\n });\n\n // Store current value in ref for stable setValue reference\n const storedValueRef = useRef<T>(storedValue);\n storedValueRef.current = storedValue;\n\n // setValue - stable reference (only depends on key)\n const setValue = useCallback<React.Dispatch<React.SetStateAction<T>>>(\n (value) => {\n try {\n const currentValue = storedValueRef.current;\n const valueToStore =\n value instanceof Function ? value(currentValue) : value;\n\n setStoredValue(valueToStore);\n\n if (typeof window !== \"undefined\") {\n window.sessionStorage.setItem(\n key,\n serializerRef.current(valueToStore)\n );\n }\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n },\n [key]\n );\n\n // removeValue - stable reference\n const removeValue = useCallback(() => {\n try {\n const initial = resolveInitialValue(initialValueRef.current);\n setStoredValue(initial);\n\n if (typeof window !== \"undefined\") {\n window.sessionStorage.removeItem(key);\n }\n } catch (error) {\n onErrorRef.current?.(error as Error);\n }\n }, [key]);\n\n // Re-read value when key changes\n useEffect(() => {\n if (!isClient) {\n return;\n }\n\n try {\n const item = window.sessionStorage.getItem(key);\n if (item !== null) {\n setStoredValue(deserializerRef.current(item));\n } else {\n setStoredValue(resolveInitialValue(initialValueRef.current));\n }\n } catch {\n setStoredValue(resolveInitialValue(initialValueRef.current));\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [key]);\n\n return [storedValue, setValue, removeValue] as const;\n}\n"],"mappings":";AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AA0CzD,SAAS,oBAAuB,cAAkC;AAChE,SAAO,OAAO,iBAAiB,aAC1B,aAAyB,IAC1B;AACN;AA0DO,SAAS,kBACd,KACA,cACA,UAAuC,CAAC,GACZ;AAC5B,QAAM;AAAA,IACJ,aAAa,KAAK;AAAA,IAClB,eAAe,KAAK;AAAA,IACpB;AAAA,EACF,IAAI;AAGJ,QAAM,gBAAgB,OAAO,UAAU;AACvC,QAAM,kBAAkB,OAAO,YAAY;AAC3C,QAAM,aAAa,OAAO,OAAO;AACjC,gBAAc,UAAU;AACxB,kBAAgB,UAAU;AAC1B,aAAW,UAAU;AAGrB,QAAM,kBAAkB,OAAO,YAAY;AAC3C,kBAAgB,UAAU;AAG1B,QAAM,WAAW,OAAO,WAAW;AAGnC,QAAM,CAAC,aAAa,cAAc,IAAI,SAAY,MAAM;AACtD,QAAI,CAAC,UAAU;AACb,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,eAAO,gBAAgB,QAAQ,IAAI;AAAA,MACrC;AACA,aAAO,oBAAoB,YAAY;AAAA,IACzC,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AACnC,aAAO,oBAAoB,YAAY;AAAA,IACzC;AAAA,EACF,CAAC;AAGD,QAAM,iBAAiB,OAAU,WAAW;AAC5C,iBAAe,UAAU;AAGzB,QAAM,WAAW;AAAA,IACf,CAAC,UAAU;AACT,UAAI;AACF,cAAM,eAAe,eAAe;AACpC,cAAM,eACJ,iBAAiB,WAAW,MAAM,YAAY,IAAI;AAEpD,uBAAe,YAAY;AAE3B,YAAI,OAAO,WAAW,aAAa;AACjC,iBAAO,eAAe;AAAA,YACpB;AAAA,YACA,cAAc,QAAQ,YAAY;AAAA,UACpC;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,mBAAW,UAAU,KAAc;AAAA,MACrC;AAAA,IACF;AAAA,IACA,CAAC,GAAG;AAAA,EACN;AAGA,QAAM,cAAc,YAAY,MAAM;AACpC,QAAI;AACF,YAAM,UAAU,oBAAoB,gBAAgB,OAAO;AAC3D,qBAAe,OAAO;AAEtB,UAAI,OAAO,WAAW,aAAa;AACjC,eAAO,eAAe,WAAW,GAAG;AAAA,MACtC;AAAA,IACF,SAAS,OAAO;AACd,iBAAW,UAAU,KAAc;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,GAAG,CAAC;AAGR,YAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAEA,QAAI;AACF,YAAM,OAAO,OAAO,eAAe,QAAQ,GAAG;AAC9C,UAAI,SAAS,MAAM;AACjB,uBAAe,gBAAgB,QAAQ,IAAI,CAAC;AAAA,MAC9C,OAAO;AACL,uBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,MAC7D;AAAA,IACF,QAAQ;AACN,qBAAe,oBAAoB,gBAAgB,OAAO,CAAC;AAAA,IAC7D;AAAA,EAEF,GAAG,CAAC,GAAG,CAAC;AAER,SAAO,CAAC,aAAa,UAAU,WAAW;AAC5C;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefy/use-session-storage",
3
- "version": "0.0.7",
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",