@usefy/use-session-storage 0.0.2

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.
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Type for initial value that can be a value or a function returning a value (lazy initialization)
3
+ */
4
+ type InitialValue<T> = T | (() => T);
5
+ /**
6
+ * Options for useSessionStorage hook
7
+ */
8
+ interface UseSessionStorageOptions<T> {
9
+ /**
10
+ * Custom serializer function for converting value to string
11
+ * @default JSON.stringify
12
+ */
13
+ serializer?: (value: T) => string;
14
+ /**
15
+ * Custom deserializer function for parsing stored string to value
16
+ * @default JSON.parse
17
+ */
18
+ deserializer?: (value: string) => T;
19
+ /**
20
+ * Callback function called when an error occurs
21
+ */
22
+ onError?: (error: Error) => void;
23
+ }
24
+ /**
25
+ * Return type for useSessionStorage hook - tuple similar to useState
26
+ */
27
+ type UseSessionStorageReturn<T> = readonly [
28
+ /** Current stored value */
29
+ T,
30
+ /** Function to update the value (same signature as useState setter) */
31
+ React.Dispatch<React.SetStateAction<T>>,
32
+ /** Function to remove the value from sessionStorage */
33
+ () => void
34
+ ];
35
+ /**
36
+ * A hook for persisting state in sessionStorage.
37
+ * Works like useState but persists the value in sessionStorage for the duration of the browser session.
38
+ *
39
+ * Unlike localStorage, sessionStorage data:
40
+ * - Is cleared when the tab/window is closed
41
+ * - Is not shared between tabs (each tab has its own session)
42
+ *
43
+ * @template T - The type of the stored value
44
+ * @param key - The sessionStorage key to store the value under
45
+ * @param initialValue - Initial value or function returning initial value (lazy initialization)
46
+ * @param options - Configuration options for serialization and error handling
47
+ * @returns A tuple of [storedValue, setValue, removeValue]
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * // Basic usage - form data that persists during session
52
+ * function CheckoutForm() {
53
+ * const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {
54
+ * name: '',
55
+ * email: '',
56
+ * });
57
+ *
58
+ * return (
59
+ * <form>
60
+ * <input
61
+ * value={formData.name}
62
+ * onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
63
+ * />
64
+ * <button type="button" onClick={clearForm}>Clear</button>
65
+ * </form>
66
+ * );
67
+ * }
68
+ * ```
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * // Temporary state that resets on tab close
73
+ * const [wizardStep, setWizardStep] = useSessionStorage('wizard-step', 1);
74
+ * ```
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * // With lazy initialization
79
+ * const [cache, setCache] = useSessionStorage('cache', () => computeInitialCache());
80
+ * ```
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * // With custom serializer/deserializer
85
+ * const [date, setDate] = useSessionStorage<Date>('lastAction', new Date(), {
86
+ * serializer: (d) => d.toISOString(),
87
+ * deserializer: (s) => new Date(s),
88
+ * });
89
+ * ```
90
+ */
91
+ declare function useSessionStorage<T>(key: string, initialValue: InitialValue<T>, options?: UseSessionStorageOptions<T>): UseSessionStorageReturn<T>;
92
+
93
+ export { type InitialValue, type UseSessionStorageOptions, type UseSessionStorageReturn, useSessionStorage };
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Type for initial value that can be a value or a function returning a value (lazy initialization)
3
+ */
4
+ type InitialValue<T> = T | (() => T);
5
+ /**
6
+ * Options for useSessionStorage hook
7
+ */
8
+ interface UseSessionStorageOptions<T> {
9
+ /**
10
+ * Custom serializer function for converting value to string
11
+ * @default JSON.stringify
12
+ */
13
+ serializer?: (value: T) => string;
14
+ /**
15
+ * Custom deserializer function for parsing stored string to value
16
+ * @default JSON.parse
17
+ */
18
+ deserializer?: (value: string) => T;
19
+ /**
20
+ * Callback function called when an error occurs
21
+ */
22
+ onError?: (error: Error) => void;
23
+ }
24
+ /**
25
+ * Return type for useSessionStorage hook - tuple similar to useState
26
+ */
27
+ type UseSessionStorageReturn<T> = readonly [
28
+ /** Current stored value */
29
+ T,
30
+ /** Function to update the value (same signature as useState setter) */
31
+ React.Dispatch<React.SetStateAction<T>>,
32
+ /** Function to remove the value from sessionStorage */
33
+ () => void
34
+ ];
35
+ /**
36
+ * A hook for persisting state in sessionStorage.
37
+ * Works like useState but persists the value in sessionStorage for the duration of the browser session.
38
+ *
39
+ * Unlike localStorage, sessionStorage data:
40
+ * - Is cleared when the tab/window is closed
41
+ * - Is not shared between tabs (each tab has its own session)
42
+ *
43
+ * @template T - The type of the stored value
44
+ * @param key - The sessionStorage key to store the value under
45
+ * @param initialValue - Initial value or function returning initial value (lazy initialization)
46
+ * @param options - Configuration options for serialization and error handling
47
+ * @returns A tuple of [storedValue, setValue, removeValue]
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * // Basic usage - form data that persists during session
52
+ * function CheckoutForm() {
53
+ * const [formData, setFormData, clearForm] = useSessionStorage('checkout-form', {
54
+ * name: '',
55
+ * email: '',
56
+ * });
57
+ *
58
+ * return (
59
+ * <form>
60
+ * <input
61
+ * value={formData.name}
62
+ * onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
63
+ * />
64
+ * <button type="button" onClick={clearForm}>Clear</button>
65
+ * </form>
66
+ * );
67
+ * }
68
+ * ```
69
+ *
70
+ * @example
71
+ * ```tsx
72
+ * // Temporary state that resets on tab close
73
+ * const [wizardStep, setWizardStep] = useSessionStorage('wizard-step', 1);
74
+ * ```
75
+ *
76
+ * @example
77
+ * ```tsx
78
+ * // With lazy initialization
79
+ * const [cache, setCache] = useSessionStorage('cache', () => computeInitialCache());
80
+ * ```
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * // With custom serializer/deserializer
85
+ * const [date, setDate] = useSessionStorage<Date>('lastAction', new Date(), {
86
+ * serializer: (d) => d.toISOString(),
87
+ * deserializer: (s) => new Date(s),
88
+ * });
89
+ * ```
90
+ */
91
+ declare function useSessionStorage<T>(key: string, initialValue: InitialValue<T>, options?: UseSessionStorageOptions<T>): UseSessionStorageReturn<T>;
92
+
93
+ export { type InitialValue, type UseSessionStorageOptions, type UseSessionStorageReturn, useSessionStorage };
package/dist/index.js ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ useSessionStorage: () => useSessionStorage
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/useSessionStorage.ts
28
+ var import_react = require("react");
29
+ function resolveInitialValue(initialValue) {
30
+ return typeof initialValue === "function" ? initialValue() : initialValue;
31
+ }
32
+ function useSessionStorage(key, initialValue, options = {}) {
33
+ const {
34
+ serializer = JSON.stringify,
35
+ deserializer = JSON.parse,
36
+ onError
37
+ } = options;
38
+ const serializerRef = (0, import_react.useRef)(serializer);
39
+ const deserializerRef = (0, import_react.useRef)(deserializer);
40
+ const onErrorRef = (0, import_react.useRef)(onError);
41
+ serializerRef.current = serializer;
42
+ deserializerRef.current = deserializer;
43
+ onErrorRef.current = onError;
44
+ const initialValueRef = (0, import_react.useRef)(initialValue);
45
+ initialValueRef.current = initialValue;
46
+ const isClient = typeof window !== "undefined";
47
+ const [storedValue, setStoredValue] = (0, import_react.useState)(() => {
48
+ if (!isClient) {
49
+ return resolveInitialValue(initialValue);
50
+ }
51
+ try {
52
+ const item = window.sessionStorage.getItem(key);
53
+ if (item !== null) {
54
+ return deserializerRef.current(item);
55
+ }
56
+ return resolveInitialValue(initialValue);
57
+ } catch (error) {
58
+ onErrorRef.current?.(error);
59
+ return resolveInitialValue(initialValue);
60
+ }
61
+ });
62
+ const storedValueRef = (0, import_react.useRef)(storedValue);
63
+ storedValueRef.current = storedValue;
64
+ const setValue = (0, import_react.useCallback)(
65
+ (value) => {
66
+ try {
67
+ const currentValue = storedValueRef.current;
68
+ const valueToStore = value instanceof Function ? value(currentValue) : value;
69
+ setStoredValue(valueToStore);
70
+ if (typeof window !== "undefined") {
71
+ window.sessionStorage.setItem(
72
+ key,
73
+ serializerRef.current(valueToStore)
74
+ );
75
+ }
76
+ } catch (error) {
77
+ onErrorRef.current?.(error);
78
+ }
79
+ },
80
+ [key]
81
+ );
82
+ const removeValue = (0, import_react.useCallback)(() => {
83
+ try {
84
+ const initial = resolveInitialValue(initialValueRef.current);
85
+ setStoredValue(initial);
86
+ if (typeof window !== "undefined") {
87
+ window.sessionStorage.removeItem(key);
88
+ }
89
+ } catch (error) {
90
+ onErrorRef.current?.(error);
91
+ }
92
+ }, [key]);
93
+ (0, import_react.useEffect)(() => {
94
+ if (!isClient) {
95
+ return;
96
+ }
97
+ try {
98
+ const item = window.sessionStorage.getItem(key);
99
+ if (item !== null) {
100
+ setStoredValue(deserializerRef.current(item));
101
+ } else {
102
+ setStoredValue(resolveInitialValue(initialValueRef.current));
103
+ }
104
+ } catch {
105
+ setStoredValue(resolveInitialValue(initialValueRef.current));
106
+ }
107
+ }, [key]);
108
+ return [storedValue, setValue, removeValue];
109
+ }
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ useSessionStorage
113
+ });
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,87 @@
1
+ // src/useSessionStorage.ts
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ function resolveInitialValue(initialValue) {
4
+ return typeof initialValue === "function" ? initialValue() : initialValue;
5
+ }
6
+ function useSessionStorage(key, initialValue, options = {}) {
7
+ const {
8
+ serializer = JSON.stringify,
9
+ deserializer = JSON.parse,
10
+ onError
11
+ } = options;
12
+ const serializerRef = useRef(serializer);
13
+ const deserializerRef = useRef(deserializer);
14
+ const onErrorRef = useRef(onError);
15
+ serializerRef.current = serializer;
16
+ deserializerRef.current = deserializer;
17
+ onErrorRef.current = onError;
18
+ const initialValueRef = useRef(initialValue);
19
+ initialValueRef.current = initialValue;
20
+ const isClient = typeof window !== "undefined";
21
+ const [storedValue, setStoredValue] = useState(() => {
22
+ if (!isClient) {
23
+ return resolveInitialValue(initialValue);
24
+ }
25
+ try {
26
+ const item = window.sessionStorage.getItem(key);
27
+ if (item !== null) {
28
+ return deserializerRef.current(item);
29
+ }
30
+ return resolveInitialValue(initialValue);
31
+ } catch (error) {
32
+ onErrorRef.current?.(error);
33
+ return resolveInitialValue(initialValue);
34
+ }
35
+ });
36
+ const storedValueRef = useRef(storedValue);
37
+ storedValueRef.current = storedValue;
38
+ const setValue = useCallback(
39
+ (value) => {
40
+ try {
41
+ const currentValue = storedValueRef.current;
42
+ const valueToStore = value instanceof Function ? value(currentValue) : value;
43
+ setStoredValue(valueToStore);
44
+ if (typeof window !== "undefined") {
45
+ window.sessionStorage.setItem(
46
+ key,
47
+ serializerRef.current(valueToStore)
48
+ );
49
+ }
50
+ } catch (error) {
51
+ onErrorRef.current?.(error);
52
+ }
53
+ },
54
+ [key]
55
+ );
56
+ const removeValue = useCallback(() => {
57
+ try {
58
+ const initial = resolveInitialValue(initialValueRef.current);
59
+ setStoredValue(initial);
60
+ if (typeof window !== "undefined") {
61
+ window.sessionStorage.removeItem(key);
62
+ }
63
+ } catch (error) {
64
+ onErrorRef.current?.(error);
65
+ }
66
+ }, [key]);
67
+ useEffect(() => {
68
+ if (!isClient) {
69
+ return;
70
+ }
71
+ try {
72
+ const item = window.sessionStorage.getItem(key);
73
+ if (item !== null) {
74
+ setStoredValue(deserializerRef.current(item));
75
+ } else {
76
+ setStoredValue(resolveInitialValue(initialValueRef.current));
77
+ }
78
+ } catch {
79
+ setStoredValue(resolveInitialValue(initialValueRef.current));
80
+ }
81
+ }, [key]);
82
+ return [storedValue, setValue, removeValue];
83
+ }
84
+ export {
85
+ useSessionStorage
86
+ };
87
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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":[]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@usefy/use-session-storage",
3
+ "version": "0.0.2",
4
+ "description": "A React hook for persisting state in sessionStorage",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "sideEffects": false,
19
+ "peerDependencies": {
20
+ "react": "^18.0.0 || ^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@testing-library/jest-dom": "^6.9.1",
24
+ "@testing-library/react": "^16.3.1",
25
+ "@testing-library/user-event": "^14.6.1",
26
+ "@types/react": "^19.0.0",
27
+ "jsdom": "^27.3.0",
28
+ "react": "^19.0.0",
29
+ "rimraf": "^6.0.1",
30
+ "tsup": "^8.0.0",
31
+ "typescript": "^5.0.0",
32
+ "vitest": "^4.0.16"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/geon0529/usefy.git",
40
+ "directory": "packages/use-session-storage"
41
+ },
42
+ "license": "MIT",
43
+ "keywords": [
44
+ "react",
45
+ "hooks",
46
+ "session-storage",
47
+ "sessionStorage",
48
+ "state",
49
+ "persistence"
50
+ ],
51
+ "scripts": {
52
+ "build": "tsup",
53
+ "dev": "tsup --watch",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "typecheck": "tsc --noEmit",
57
+ "clean": "rimraf dist"
58
+ }
59
+ }