@himanshu-sorathiya/react-kit 1.0.26 → 1.0.27

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/dist/index.d.ts CHANGED
@@ -816,43 +816,295 @@ export interface UseSortReturn<T> {
816
816
  getSortIndex: (id: string) => number | undefined;
817
817
  }
818
818
  export declare function useSort<T>(data?: T[], initialSorts?: SortState): UseSortReturn<T>;
819
+ /**
820
+ * Defines how a value of type `T` is converted to and from the string
821
+ * format that `localStorage`/`sessionStorage` can actually store — the Web
822
+ * Storage API only ever stores strings.
823
+ *
824
+ * Implement this to store types the default JSON-based serializer can't
825
+ * round-trip faithfully, e.g. `Map`, `Set`, `Date`, or `bigint` — see
826
+ * `mapSerializer`, `setSerializer`, `dateSerializer`, and
827
+ * `bigIntSerializer` in `serializers.ts` for ready-made ones.
828
+ *
829
+ * @typeParam T - The in-memory value type this serializer handles.
830
+ */
819
831
  export interface StorageSerializer<T> {
832
+ /** Converts an in-memory value into the string that gets stored. */
820
833
  serialize: (value: T) => string;
834
+ /**
835
+ * Converts a stored string back into an in-memory value.
836
+ *
837
+ * @throws If the raw string can't be converted back into `T`. The hook
838
+ * catches this, falls back to `initialValue`, and reports the error —
839
+ * see `onError` on {@link BaseStorageOptions}.
840
+ */
821
841
  deserialize: (raw: string) => T;
822
842
  }
843
+ /**
844
+ * Options shared by `useLocalStorage` and `useSessionStorage`.
845
+ *
846
+ * @typeParam T - The type of value being stored.
847
+ */
823
848
  export interface BaseStorageOptions<T> {
849
+ /**
850
+ * Custom (de)serializer for values that don't round-trip through
851
+ * `JSON.stringify`/`JSON.parse` cleanly.
852
+ *
853
+ * @defaultValue `defaultSerializer` (plain `JSON.stringify`/`JSON.parse`)
854
+ */
824
855
  serializer?: StorageSerializer<T>;
856
+ /**
857
+ * Whether to synchronously read the existing stored value on mount.
858
+ *
859
+ * - `true` (default): `value` reflects storage from the very first
860
+ * render it's allowed to (see the SSR note below).
861
+ * - `false`: `value` starts as `undefined` and only reflects storage
862
+ * once `isHydrated` becomes `true`, one render after mount. Use this
863
+ * if you'd rather render a loading/skeleton state than briefly show a
864
+ * value that might change right after.
865
+ *
866
+ * Either way, on the server — and during the client's hydration render
867
+ * — `value` is always `initialValue`. This option only affects timing
868
+ * on the client, after that point.
869
+ *
870
+ * @defaultValue `true`
871
+ */
825
872
  initializeWithValue?: boolean;
873
+ /**
874
+ * Whether other instances of this hook watching the *same key* in the
875
+ * *same tab* stay in sync with each other. Implemented via a
876
+ * `CustomEvent` dispatched on `window` — the browser's native `storage`
877
+ * event never fires in the tab that made the change, so without this,
878
+ * two components reading the same key in one tab would drift apart.
879
+ *
880
+ * @defaultValue `true`
881
+ */
826
882
  sameInstanceSync?: boolean;
883
+ /**
884
+ * Called whenever the hook hits an unexpected condition: a failed
885
+ * read, a failed write, a failed cross-instance deserialize, or an
886
+ * attempt to change the storage key at runtime. Fires in every
887
+ * environment, including production — use this for telemetry/error
888
+ * reporting.
889
+ *
890
+ * This is *not* a replacement for the dev-only `console.warn` the hook
891
+ * also emits for the same conditions (visible when
892
+ * `process.env.NODE_ENV !== "production"`) — both fire independently.
893
+ */
894
+ onError?: (error: Error) => void;
827
895
  }
896
+ /**
897
+ * Payload carried by the same-tab `CustomEvent` used for
898
+ * {@link BaseStorageOptions.sameInstanceSync}. Internal — not part of the
899
+ * public hook API, but exported so `useStorageEngine.ts` can import it.
900
+ */
828
901
  export interface StorageCustomEventDetail {
902
+ /** The new serialized value, or `null` if the key was removed. */
829
903
  value: string | null;
904
+ /**
905
+ * A per-hook-instance identifier, used so an instance can recognize —
906
+ * and ignore — the event it just dispatched itself.
907
+ */
830
908
  instanceId: symbol;
831
909
  }
832
- export interface UseLocalStorageOptions<T> extends BaseStorageOptions<T> {
833
- crossInstanceSync?: boolean;
834
- }
835
- export interface UseLocalStorageReturn<T> {
910
+ /**
911
+ * The shape returned by {@link useStorageEngine} — and, re-exported, by
912
+ * both `useLocalStorage` and `useSessionStorage`.
913
+ *
914
+ * @typeParam T - The type of value being stored.
915
+ */
916
+ interface UseStorageEngineReturn<T> {
917
+ /**
918
+ * The current value.
919
+ *
920
+ * - `undefined` if nothing is stored yet and no `initialValue` was
921
+ * given, or — when `initializeWithValue: false` — before hydration
922
+ * completes.
923
+ * - On the server, and during the client's hydration render, this is
924
+ * always `initialValue`: real storage can only be read client-side,
925
+ * and reading it any earlier would produce a hydration mismatch.
926
+ */
836
927
  value: T | undefined;
928
+ /**
929
+ * Writes a new value to storage. Accepts either the value directly, or
930
+ * an updater function that receives the current value and returns the
931
+ * next one — the same convention as `useState`'s setter.
932
+ *
933
+ * A no-op if storage isn't available (SSR, or storage access blocked).
934
+ */
837
935
  setValue: (valueOrUpdater: T | ((prev: T | undefined) => T)) => void;
936
+ /**
937
+ * Removes the key from storage entirely and resets `value` back to
938
+ * whatever `initialValue` was passed to the hook.
939
+ *
940
+ * A no-op if storage isn't available (SSR, or storage access blocked).
941
+ */
838
942
  removeValue: () => void;
943
+ /**
944
+ * `true` once the client has mounted and the hook has settled on its
945
+ * real (non-server-snapshot) value. Useful for showing a loading state
946
+ * instead of a value that might change the instant hydration finishes.
947
+ */
839
948
  isHydrated: boolean;
949
+ /**
950
+ * The most recent error the hook encountered — a failed read, write,
951
+ * or cross-instance sync, or an attempted key change — or `null` if
952
+ * nothing has gone wrong (or an error was cleared by a subsequent
953
+ * successful write/remove). See `onError` on {@link BaseStorageOptions}
954
+ * for an imperative alternative to reading this reactively.
955
+ */
840
956
  error: Error | null;
841
957
  }
842
- export declare function useLocalStorage<T = unknown>(key: string, initialValue?: T, options?: UseLocalStorageOptions<T>): UseLocalStorageReturn<T>;
843
- export type UseSessionStorageOptions<T> = BaseStorageOptions<T>;
844
- export interface UseSessionStorageReturn<T> {
845
- value: T | undefined;
846
- setValue: (valueOrUpdater: T | ((prev: T | undefined) => T)) => void;
847
- removeValue: () => void;
848
- isHydrated: boolean;
849
- error: Error | null;
958
+ /**
959
+ * Options accepted by `useLocalStorage`.
960
+ *
961
+ * @typeParam T - The type of value being stored.
962
+ */
963
+ export interface UseLocalStorageOptions<T> extends BaseStorageOptions<T> {
964
+ /**
965
+ * Whether this hook instance should sync with the same key changing in
966
+ * *other tabs/windows* on the same origin, via the browser's native
967
+ * `storage` event. Has no `sessionStorage` equivalent — sessionStorage
968
+ * isn't shared across tabs, so there's nothing to sync in that case.
969
+ *
970
+ * @defaultValue `true`
971
+ */
972
+ crossInstanceSync?: boolean;
850
973
  }
851
- export declare function useSessionStorage<T = unknown>(key: string, initialValue?: T, options?: UseSessionStorageOptions<T>): UseSessionStorageReturn<T>;
974
+ /**
975
+ * Reads and writes a `localStorage` key, kept in sync with React state.
976
+ *
977
+ * - Persists across page reloads and browser restarts (unlike
978
+ * `useSessionStorage`).
979
+ * - Stays in sync with every component in the current tab watching the
980
+ * same key — see {@link UseLocalStorageOptions.sameInstanceSync} — and
981
+ * with other tabs/windows on the same origin — see
982
+ * {@link UseLocalStorageOptions.crossInstanceSync}.
983
+ * - Safe under SSR: on the server, and during the client's hydration
984
+ * render, `value` is always `initialValue`. The real stored value is
985
+ * only read client-side, immediately after hydration.
986
+ *
987
+ * @typeParam T - The type of value being stored. Defaults to `unknown` if
988
+ * omitted — pass an explicit type argument for anything beyond ad-hoc use.
989
+ * @param key - The `localStorage` key to read and write. Changing this on
990
+ * a later render isn't supported; the hook warns (dev console + `onError`)
991
+ * and keeps using the original key if you do.
992
+ * @param initialValue - Used when nothing is stored yet, as the value
993
+ * shown before hydration completes, and as what `removeValue` resets to.
994
+ * @param options - See {@link UseLocalStorageOptions}.
995
+ * @returns `{ value, setValue, removeValue, isHydrated, error }`.
996
+ *
997
+ * @example
998
+ * Basic usage:
999
+ * ```tsx
1000
+ * const { value: theme, setValue: setTheme } = useLocalStorage<"light" | "dark">("theme", "light");
1001
+ *
1002
+ * <button onClick={() => setTheme(prev => (prev === "light" ? "dark" : "light"))}>
1003
+ * Toggle theme
1004
+ * </button>
1005
+ * ```
1006
+ *
1007
+ * @example
1008
+ * With a custom serializer and error reporting:
1009
+ * ```tsx
1010
+ * const { value, setValue, error } = useLocalStorage("lastSeen", new Date(), {
1011
+ * serializer: dateSerializer,
1012
+ * onError: (err) => reportToErrorTracker(err),
1013
+ * });
1014
+ * ```
1015
+ */
1016
+ export declare function useLocalStorage<T = unknown>(key: string, initialValue?: T, options?: UseLocalStorageOptions<T>): UseStorageEngineReturn<T>;
1017
+ /**
1018
+ * Options accepted by `useSessionStorage`. Identical to
1019
+ * `BaseStorageOptions` — unlike `UseLocalStorageOptions`, there's no
1020
+ * `crossInstanceSync` option here, since sessionStorage isn't shared
1021
+ * across tabs in the first place.
1022
+ *
1023
+ * @typeParam T - The type of value being stored.
1024
+ */
1025
+ export type UseSessionStorageOptions<T> = BaseStorageOptions<T>;
1026
+ /**
1027
+ * Reads and writes a `sessionStorage` key, kept in sync with React state.
1028
+ *
1029
+ * - Scoped to the current tab: cleared when the tab closes, and not
1030
+ * shared with other tabs (unlike `useLocalStorage`).
1031
+ * - Stays in sync with every component in the current tab watching the
1032
+ * same key — see {@link UseSessionStorageOptions.sameInstanceSync}.
1033
+ * - Safe under SSR: on the server, and during the client's hydration
1034
+ * render, `value` is always `initialValue`. The real stored value is
1035
+ * only read client-side, immediately after hydration.
1036
+ *
1037
+ * @typeParam T - The type of value being stored. Defaults to `unknown` if
1038
+ * omitted — pass an explicit type argument for anything beyond ad-hoc use.
1039
+ * @param key - The `sessionStorage` key to read and write. Changing this
1040
+ * on a later render isn't supported; the hook warns (dev console +
1041
+ * `onError`) and keeps using the original key if you do.
1042
+ * @param initialValue - Used when nothing is stored yet, as the value
1043
+ * shown before hydration completes, and as what `removeValue` resets to.
1044
+ * @param options - See {@link UseSessionStorageOptions}.
1045
+ * @returns `{ value, setValue, removeValue, isHydrated, error }`.
1046
+ *
1047
+ * @example
1048
+ * ```tsx
1049
+ * const { value: draft, setValue: setDraft } = useSessionStorage("draft-comment", "");
1050
+ *
1051
+ * <textarea value={draft ?? ""} onChange={(e) => setDraft(e.target.value)} />
1052
+ * ```
1053
+ */
1054
+ export declare function useSessionStorage<T = unknown>(key: string, initialValue?: T, options?: UseSessionStorageOptions<T>): UseStorageEngineReturn<T>;
1055
+ /**
1056
+ * The default serializer used when no `serializer` option is passed to
1057
+ * `useLocalStorage`/`useSessionStorage`. Plain `JSON.stringify`/
1058
+ * `JSON.parse` — works for any JSON-safe value (objects, arrays, strings,
1059
+ * numbers, booleans, `null`), but not `Map`, `Set`, `Date`, `bigint`, or
1060
+ * `undefined` (see the other serializers below for those).
1061
+ */
852
1062
  export declare const defaultSerializer: StorageSerializer<unknown>;
1063
+ /**
1064
+ * Serializer for `Map` values. `JSON.stringify` can't handle `Map`
1065
+ * directly, so this round-trips it via an array of `[key, value]` entries.
1066
+ *
1067
+ * @typeParam K - The map's key type.
1068
+ * @typeParam V - The map's value type.
1069
+ *
1070
+ * @example
1071
+ * ```ts
1072
+ * useLocalStorage("tags", new Map<string, number>(), {
1073
+ * serializer: mapSerializer<string, number>(),
1074
+ * });
1075
+ * ```
1076
+ */
853
1077
  export declare function mapSerializer<K, V>(): StorageSerializer<Map<K, V>>;
1078
+ /**
1079
+ * Serializer for `Set` values, round-tripped via a plain array.
1080
+ *
1081
+ * @typeParam V - The set's value type.
1082
+ *
1083
+ * @example
1084
+ * ```ts
1085
+ * useLocalStorage("selectedIds", new Set<string>(), {
1086
+ * serializer: setSerializer<string>(),
1087
+ * });
1088
+ * ```
1089
+ */
854
1090
  export declare function setSerializer<V>(): StorageSerializer<Set<V>>;
1091
+ /**
1092
+ * Serializer for `Date` values, stored as an ISO 8601 string
1093
+ * (`Date.prototype.toISOString`).
1094
+ *
1095
+ * @throws During `deserialize`, if the stored string isn't a valid date —
1096
+ * caught by the hook, which falls back to `initialValue` and reports the
1097
+ * error via `onError`/the dev console warning.
1098
+ */
855
1099
  export declare const dateSerializer: StorageSerializer<Date>;
1100
+ /**
1101
+ * Serializer for `bigint` values. `JSON.stringify` throws on `bigint`
1102
+ * values, so this stores them as a plain decimal string instead.
1103
+ *
1104
+ * @throws During `deserialize`, if the stored string can't be converted to
1105
+ * a `bigint` — caught by the hook, which falls back to `initialValue` and
1106
+ * reports the error via `onError`/the dev console warning.
1107
+ */
856
1108
  export declare const bigIntSerializer: StorageSerializer<bigint>;
857
1109
  export interface FuzzyHighlighterProps {
858
1110
  text: string;
@@ -948,4 +1200,9 @@ export declare function useVisibility<T = unknown>(options?: {
948
1200
  initialVisibleIds?: VisibilityId[];
949
1201
  }): UseVisibilityReturn<T>;
950
1202
 
1203
+ export {
1204
+ UseStorageEngineReturn as UseLocalStorageReturn,
1205
+ UseStorageEngineReturn as UseSessionStorageReturn,
1206
+ };
1207
+
951
1208
  export {};
package/dist/storage.d.ts CHANGED
@@ -1,42 +1,299 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ /**
4
+ * Defines how a value of type `T` is converted to and from the string
5
+ * format that `localStorage`/`sessionStorage` can actually store — the Web
6
+ * Storage API only ever stores strings.
7
+ *
8
+ * Implement this to store types the default JSON-based serializer can't
9
+ * round-trip faithfully, e.g. `Map`, `Set`, `Date`, or `bigint` — see
10
+ * `mapSerializer`, `setSerializer`, `dateSerializer`, and
11
+ * `bigIntSerializer` in `serializers.ts` for ready-made ones.
12
+ *
13
+ * @typeParam T - The in-memory value type this serializer handles.
14
+ */
3
15
  export interface StorageSerializer<T> {
16
+ /** Converts an in-memory value into the string that gets stored. */
4
17
  serialize: (value: T) => string;
18
+ /**
19
+ * Converts a stored string back into an in-memory value.
20
+ *
21
+ * @throws If the raw string can't be converted back into `T`. The hook
22
+ * catches this, falls back to `initialValue`, and reports the error —
23
+ * see `onError` on {@link BaseStorageOptions}.
24
+ */
5
25
  deserialize: (raw: string) => T;
6
26
  }
27
+ /**
28
+ * Options shared by `useLocalStorage` and `useSessionStorage`.
29
+ *
30
+ * @typeParam T - The type of value being stored.
31
+ */
7
32
  export interface BaseStorageOptions<T> {
33
+ /**
34
+ * Custom (de)serializer for values that don't round-trip through
35
+ * `JSON.stringify`/`JSON.parse` cleanly.
36
+ *
37
+ * @defaultValue `defaultSerializer` (plain `JSON.stringify`/`JSON.parse`)
38
+ */
8
39
  serializer?: StorageSerializer<T>;
40
+ /**
41
+ * Whether to synchronously read the existing stored value on mount.
42
+ *
43
+ * - `true` (default): `value` reflects storage from the very first
44
+ * render it's allowed to (see the SSR note below).
45
+ * - `false`: `value` starts as `undefined` and only reflects storage
46
+ * once `isHydrated` becomes `true`, one render after mount. Use this
47
+ * if you'd rather render a loading/skeleton state than briefly show a
48
+ * value that might change right after.
49
+ *
50
+ * Either way, on the server — and during the client's hydration render
51
+ * — `value` is always `initialValue`. This option only affects timing
52
+ * on the client, after that point.
53
+ *
54
+ * @defaultValue `true`
55
+ */
9
56
  initializeWithValue?: boolean;
57
+ /**
58
+ * Whether other instances of this hook watching the *same key* in the
59
+ * *same tab* stay in sync with each other. Implemented via a
60
+ * `CustomEvent` dispatched on `window` — the browser's native `storage`
61
+ * event never fires in the tab that made the change, so without this,
62
+ * two components reading the same key in one tab would drift apart.
63
+ *
64
+ * @defaultValue `true`
65
+ */
10
66
  sameInstanceSync?: boolean;
67
+ /**
68
+ * Called whenever the hook hits an unexpected condition: a failed
69
+ * read, a failed write, a failed cross-instance deserialize, or an
70
+ * attempt to change the storage key at runtime. Fires in every
71
+ * environment, including production — use this for telemetry/error
72
+ * reporting.
73
+ *
74
+ * This is *not* a replacement for the dev-only `console.warn` the hook
75
+ * also emits for the same conditions (visible when
76
+ * `process.env.NODE_ENV !== "production"`) — both fire independently.
77
+ */
78
+ onError?: (error: Error) => void;
11
79
  }
80
+ /**
81
+ * Payload carried by the same-tab `CustomEvent` used for
82
+ * {@link BaseStorageOptions.sameInstanceSync}. Internal — not part of the
83
+ * public hook API, but exported so `useStorageEngine.ts` can import it.
84
+ */
12
85
  export interface StorageCustomEventDetail {
86
+ /** The new serialized value, or `null` if the key was removed. */
13
87
  value: string | null;
88
+ /**
89
+ * A per-hook-instance identifier, used so an instance can recognize —
90
+ * and ignore — the event it just dispatched itself.
91
+ */
14
92
  instanceId: symbol;
15
93
  }
16
- export interface UseLocalStorageOptions<T> extends BaseStorageOptions<T> {
17
- crossInstanceSync?: boolean;
18
- }
19
- export interface UseLocalStorageReturn<T> {
94
+ /**
95
+ * The shape returned by {@link useStorageEngine} — and, re-exported, by
96
+ * both `useLocalStorage` and `useSessionStorage`.
97
+ *
98
+ * @typeParam T - The type of value being stored.
99
+ */
100
+ interface UseStorageEngineReturn<T> {
101
+ /**
102
+ * The current value.
103
+ *
104
+ * - `undefined` if nothing is stored yet and no `initialValue` was
105
+ * given, or — when `initializeWithValue: false` — before hydration
106
+ * completes.
107
+ * - On the server, and during the client's hydration render, this is
108
+ * always `initialValue`: real storage can only be read client-side,
109
+ * and reading it any earlier would produce a hydration mismatch.
110
+ */
20
111
  value: T | undefined;
112
+ /**
113
+ * Writes a new value to storage. Accepts either the value directly, or
114
+ * an updater function that receives the current value and returns the
115
+ * next one — the same convention as `useState`'s setter.
116
+ *
117
+ * A no-op if storage isn't available (SSR, or storage access blocked).
118
+ */
21
119
  setValue: (valueOrUpdater: T | ((prev: T | undefined) => T)) => void;
120
+ /**
121
+ * Removes the key from storage entirely and resets `value` back to
122
+ * whatever `initialValue` was passed to the hook.
123
+ *
124
+ * A no-op if storage isn't available (SSR, or storage access blocked).
125
+ */
22
126
  removeValue: () => void;
127
+ /**
128
+ * `true` once the client has mounted and the hook has settled on its
129
+ * real (non-server-snapshot) value. Useful for showing a loading state
130
+ * instead of a value that might change the instant hydration finishes.
131
+ */
23
132
  isHydrated: boolean;
133
+ /**
134
+ * The most recent error the hook encountered — a failed read, write,
135
+ * or cross-instance sync, or an attempted key change — or `null` if
136
+ * nothing has gone wrong (or an error was cleared by a subsequent
137
+ * successful write/remove). See `onError` on {@link BaseStorageOptions}
138
+ * for an imperative alternative to reading this reactively.
139
+ */
24
140
  error: Error | null;
25
141
  }
26
- export declare function useLocalStorage<T = unknown>(key: string, initialValue?: T, options?: UseLocalStorageOptions<T>): UseLocalStorageReturn<T>;
27
- export type UseSessionStorageOptions<T> = BaseStorageOptions<T>;
28
- export interface UseSessionStorageReturn<T> {
29
- value: T | undefined;
30
- setValue: (valueOrUpdater: T | ((prev: T | undefined) => T)) => void;
31
- removeValue: () => void;
32
- isHydrated: boolean;
33
- error: Error | null;
142
+ /**
143
+ * Options accepted by `useLocalStorage`.
144
+ *
145
+ * @typeParam T - The type of value being stored.
146
+ */
147
+ export interface UseLocalStorageOptions<T> extends BaseStorageOptions<T> {
148
+ /**
149
+ * Whether this hook instance should sync with the same key changing in
150
+ * *other tabs/windows* on the same origin, via the browser's native
151
+ * `storage` event. Has no `sessionStorage` equivalent — sessionStorage
152
+ * isn't shared across tabs, so there's nothing to sync in that case.
153
+ *
154
+ * @defaultValue `true`
155
+ */
156
+ crossInstanceSync?: boolean;
34
157
  }
35
- export declare function useSessionStorage<T = unknown>(key: string, initialValue?: T, options?: UseSessionStorageOptions<T>): UseSessionStorageReturn<T>;
158
+ /**
159
+ * Reads and writes a `localStorage` key, kept in sync with React state.
160
+ *
161
+ * - Persists across page reloads and browser restarts (unlike
162
+ * `useSessionStorage`).
163
+ * - Stays in sync with every component in the current tab watching the
164
+ * same key — see {@link UseLocalStorageOptions.sameInstanceSync} — and
165
+ * with other tabs/windows on the same origin — see
166
+ * {@link UseLocalStorageOptions.crossInstanceSync}.
167
+ * - Safe under SSR: on the server, and during the client's hydration
168
+ * render, `value` is always `initialValue`. The real stored value is
169
+ * only read client-side, immediately after hydration.
170
+ *
171
+ * @typeParam T - The type of value being stored. Defaults to `unknown` if
172
+ * omitted — pass an explicit type argument for anything beyond ad-hoc use.
173
+ * @param key - The `localStorage` key to read and write. Changing this on
174
+ * a later render isn't supported; the hook warns (dev console + `onError`)
175
+ * and keeps using the original key if you do.
176
+ * @param initialValue - Used when nothing is stored yet, as the value
177
+ * shown before hydration completes, and as what `removeValue` resets to.
178
+ * @param options - See {@link UseLocalStorageOptions}.
179
+ * @returns `{ value, setValue, removeValue, isHydrated, error }`.
180
+ *
181
+ * @example
182
+ * Basic usage:
183
+ * ```tsx
184
+ * const { value: theme, setValue: setTheme } = useLocalStorage<"light" | "dark">("theme", "light");
185
+ *
186
+ * <button onClick={() => setTheme(prev => (prev === "light" ? "dark" : "light"))}>
187
+ * Toggle theme
188
+ * </button>
189
+ * ```
190
+ *
191
+ * @example
192
+ * With a custom serializer and error reporting:
193
+ * ```tsx
194
+ * const { value, setValue, error } = useLocalStorage("lastSeen", new Date(), {
195
+ * serializer: dateSerializer,
196
+ * onError: (err) => reportToErrorTracker(err),
197
+ * });
198
+ * ```
199
+ */
200
+ export declare function useLocalStorage<T = unknown>(key: string, initialValue?: T, options?: UseLocalStorageOptions<T>): UseStorageEngineReturn<T>;
201
+ /**
202
+ * Options accepted by `useSessionStorage`. Identical to
203
+ * `BaseStorageOptions` — unlike `UseLocalStorageOptions`, there's no
204
+ * `crossInstanceSync` option here, since sessionStorage isn't shared
205
+ * across tabs in the first place.
206
+ *
207
+ * @typeParam T - The type of value being stored.
208
+ */
209
+ export type UseSessionStorageOptions<T> = BaseStorageOptions<T>;
210
+ /**
211
+ * Reads and writes a `sessionStorage` key, kept in sync with React state.
212
+ *
213
+ * - Scoped to the current tab: cleared when the tab closes, and not
214
+ * shared with other tabs (unlike `useLocalStorage`).
215
+ * - Stays in sync with every component in the current tab watching the
216
+ * same key — see {@link UseSessionStorageOptions.sameInstanceSync}.
217
+ * - Safe under SSR: on the server, and during the client's hydration
218
+ * render, `value` is always `initialValue`. The real stored value is
219
+ * only read client-side, immediately after hydration.
220
+ *
221
+ * @typeParam T - The type of value being stored. Defaults to `unknown` if
222
+ * omitted — pass an explicit type argument for anything beyond ad-hoc use.
223
+ * @param key - The `sessionStorage` key to read and write. Changing this
224
+ * on a later render isn't supported; the hook warns (dev console +
225
+ * `onError`) and keeps using the original key if you do.
226
+ * @param initialValue - Used when nothing is stored yet, as the value
227
+ * shown before hydration completes, and as what `removeValue` resets to.
228
+ * @param options - See {@link UseSessionStorageOptions}.
229
+ * @returns `{ value, setValue, removeValue, isHydrated, error }`.
230
+ *
231
+ * @example
232
+ * ```tsx
233
+ * const { value: draft, setValue: setDraft } = useSessionStorage("draft-comment", "");
234
+ *
235
+ * <textarea value={draft ?? ""} onChange={(e) => setDraft(e.target.value)} />
236
+ * ```
237
+ */
238
+ export declare function useSessionStorage<T = unknown>(key: string, initialValue?: T, options?: UseSessionStorageOptions<T>): UseStorageEngineReturn<T>;
239
+ /**
240
+ * The default serializer used when no `serializer` option is passed to
241
+ * `useLocalStorage`/`useSessionStorage`. Plain `JSON.stringify`/
242
+ * `JSON.parse` — works for any JSON-safe value (objects, arrays, strings,
243
+ * numbers, booleans, `null`), but not `Map`, `Set`, `Date`, `bigint`, or
244
+ * `undefined` (see the other serializers below for those).
245
+ */
36
246
  export declare const defaultSerializer: StorageSerializer<unknown>;
247
+ /**
248
+ * Serializer for `Map` values. `JSON.stringify` can't handle `Map`
249
+ * directly, so this round-trips it via an array of `[key, value]` entries.
250
+ *
251
+ * @typeParam K - The map's key type.
252
+ * @typeParam V - The map's value type.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * useLocalStorage("tags", new Map<string, number>(), {
257
+ * serializer: mapSerializer<string, number>(),
258
+ * });
259
+ * ```
260
+ */
37
261
  export declare function mapSerializer<K, V>(): StorageSerializer<Map<K, V>>;
262
+ /**
263
+ * Serializer for `Set` values, round-tripped via a plain array.
264
+ *
265
+ * @typeParam V - The set's value type.
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * useLocalStorage("selectedIds", new Set<string>(), {
270
+ * serializer: setSerializer<string>(),
271
+ * });
272
+ * ```
273
+ */
38
274
  export declare function setSerializer<V>(): StorageSerializer<Set<V>>;
275
+ /**
276
+ * Serializer for `Date` values, stored as an ISO 8601 string
277
+ * (`Date.prototype.toISOString`).
278
+ *
279
+ * @throws During `deserialize`, if the stored string isn't a valid date —
280
+ * caught by the hook, which falls back to `initialValue` and reports the
281
+ * error via `onError`/the dev console warning.
282
+ */
39
283
  export declare const dateSerializer: StorageSerializer<Date>;
284
+ /**
285
+ * Serializer for `bigint` values. `JSON.stringify` throws on `bigint`
286
+ * values, so this stores them as a plain decimal string instead.
287
+ *
288
+ * @throws During `deserialize`, if the stored string can't be converted to
289
+ * a `bigint` — caught by the hook, which falls back to `initialValue` and
290
+ * reports the error via `onError`/the dev console warning.
291
+ */
40
292
  export declare const bigIntSerializer: StorageSerializer<bigint>;
41
293
 
294
+ export {
295
+ UseStorageEngineReturn as UseLocalStorageReturn,
296
+ UseStorageEngineReturn as UseSessionStorageReturn,
297
+ };
298
+
42
299
  export {};
package/dist/storage2.js CHANGED
@@ -1,30 +1,41 @@
1
- import { t as e } from "./useEventListener.js";
2
- import { useCallback as t, useEffect as n, useRef as r, useState as i } from "react";
1
+ import { useCallback as e, useEffect as t, useRef as n, useState as r, useSyncExternalStore as i } from "react";
2
+ //#region src/shared/storageShared/getStorage.ts
3
+ function a(e) {
4
+ return () => {
5
+ if (typeof window > "u") return null;
6
+ try {
7
+ return window[e];
8
+ } catch {
9
+ return null;
10
+ }
11
+ };
12
+ }
13
+ //#endregion
3
14
  //#region src/shared/storageShared/serializers.ts
4
- var a = {
15
+ var o = {
5
16
  serialize: (e) => JSON.stringify(e),
6
17
  deserialize: (e) => JSON.parse(e)
7
18
  };
8
- function o() {
19
+ function s() {
9
20
  return {
10
21
  serialize: (e) => JSON.stringify(Array.from(e.entries())),
11
22
  deserialize: (e) => new Map(JSON.parse(e))
12
23
  };
13
24
  }
14
- function s() {
25
+ function c() {
15
26
  return {
16
27
  serialize: (e) => JSON.stringify(Array.from(e.values())),
17
28
  deserialize: (e) => new Set(JSON.parse(e))
18
29
  };
19
30
  }
20
- var c = {
31
+ var l = {
21
32
  serialize: (e) => e.toISOString(),
22
33
  deserialize: (e) => {
23
34
  let t = new Date(e);
24
35
  if (isNaN(t.getTime())) throw Error(`[react-kit] dateSerializer: invalid date string "${e}"`);
25
36
  return t;
26
37
  }
27
- }, l = {
38
+ }, u = {
28
39
  serialize: (e) => e.toString(),
29
40
  deserialize: (e) => {
30
41
  try {
@@ -33,229 +44,155 @@ var c = {
33
44
  throw Error(`[react-kit] bigIntSerializer: cannot convert "${e}" to bigint`);
34
45
  }
35
46
  }
36
- }, u = "react-kit:use-local-storage";
37
- //#endregion
38
- //#region src/storage/useLocalStorage/utils.ts
39
- function d() {
40
- if (typeof window > "u") return null;
47
+ }, d = globalThis.process?.env?.NODE_ENV !== "production";
48
+ function f(e, t, n) {
41
49
  try {
42
- return window.localStorage;
50
+ return t.deserialize(e);
43
51
  } catch {
44
- return null;
52
+ return n;
45
53
  }
46
54
  }
47
- function f(e, t, n, r) {
48
- try {
49
- let i = e.getItem(t);
50
- return i === null ? {
51
- value: n,
52
- error: null
53
- } : {
54
- value: r.deserialize(i),
55
- error: null
55
+ function p(a, s, c, l = {}) {
56
+ let { serializer: u = o, initializeWithValue: p = !0, sameInstanceSync: m = !0, crossInstanceSync: h = !0, onError: g } = l, [_] = r(() => s), v = s !== _, [y] = r(() => Symbol()), b = n(u);
57
+ t(() => {
58
+ b.current = u;
59
+ }, [u]);
60
+ let x = n(c);
61
+ t(() => {
62
+ x.current = c;
63
+ }, [c]);
64
+ let S = n(g);
65
+ t(() => {
66
+ S.current = g;
67
+ }, [g]);
68
+ let [C, w] = r(null), T = e((e, t) => {
69
+ d && console.warn(`[${a.hookLabel}] ${e}:`, t.message), S.current?.(t), w(t);
70
+ }, [a.hookLabel]);
71
+ t(() => {
72
+ v && T("Ignoring key change", /* @__PURE__ */ Error(`Changing the storage key at runtime is not supported. Still using original key: "${_}". Received new key: "${s}".`));
73
+ }, [
74
+ s,
75
+ v,
76
+ _,
77
+ T
78
+ ]);
79
+ let E = n({
80
+ raw: null,
81
+ parsed: c
82
+ }), D = n(null), O = e(() => {
83
+ let e = a.getStorage();
84
+ if (!e) return x.current;
85
+ let t = e.getItem(_);
86
+ if (E.current.raw === t) return E.current.parsed;
87
+ let n = t === null ? x.current : f(t, b.current, x.current);
88
+ return E.current = {
89
+ raw: t,
90
+ parsed: n
91
+ }, n;
92
+ }, [_]), k = e(() => x.current, []), A = i(e((e) => {
93
+ if (D.current = e, typeof window > "u") return () => {
94
+ D.current = null;
56
95
  };
57
- } catch (e) {
58
- return {
59
- value: n,
60
- error: e instanceof Error ? e : Error(String(e))
96
+ let t = new AbortController();
97
+ return h && a.nativeStorageEventSupported && window.addEventListener("storage", (t) => {
98
+ t.storageArea === a.getStorage() && (t.key !== null && t.key !== _ || e());
99
+ }, { signal: t.signal }), m && window.addEventListener(`${a.customEventName}:${_}`, (t) => {
100
+ t.detail.instanceId !== y && e();
101
+ }, { signal: t.signal }), () => {
102
+ t.abort(), D.current = null;
61
103
  };
62
- }
63
- }
64
- function p(e, t, n) {
65
- let r = {
66
- value: t,
67
- instanceId: n
68
- }, i = new CustomEvent(`${u}:${e}`, { detail: r });
69
- window.dispatchEvent(i);
70
- }
71
- //#endregion
72
- //#region src/storage/useLocalStorage/useLocalStorage.ts
73
- function m(o, s, c = {}) {
74
- let { serializer: l = a, initializeWithValue: m = !0, sameInstanceSync: h = !0, crossInstanceSync: g = !0 } = c, [_] = i(() => o), v = r(_), y = o !== _, b = `${u}:${_}`, x = r(Symbol()), S = r(l);
75
- n(() => {
76
- S.current = l;
77
- }, [l]);
78
- let [C, w] = i(() => {
79
- if (!m) return;
80
- let e = d();
81
- if (!e) return s;
82
- let { value: t } = f(e, o, s, l);
83
- return t;
84
- }), T = r(C);
85
- n(() => {
86
- T.current = C;
87
- }, [C]);
88
- let [E, D] = i(m && typeof window < "u"), [O, k] = i(null);
89
- n(() => {
90
- if (!y) return;
91
- let e = /* @__PURE__ */ Error(`[react-kit:use-local-storage] Changing the storage key at runtime is not supported. Still using original key: "${v.current}". Received new key: "${o}".`);
92
- console.warn(e.message), k(e);
93
- }, [o, y]), n(() => {
94
- if (m) return;
95
- let e = d(), { value: t, error: n } = e === null ? {
96
- value: s,
97
- error: null
98
- } : f(e, v.current, s, S.current);
99
- n && (console.warn(`[react-kit:use-local-storage] Failed to read key "${v.current}" from localStorage:`, n.message), k(n)), w(t), D(!0);
100
- }, [m]);
101
- let A = t((e) => {
102
- let t = d();
103
- if (!t) return;
104
- let n = T.current, r = typeof e == "function" ? e(n) : e;
105
- try {
106
- let e = S.current.serialize(r);
107
- t.setItem(v.current, e), w(r), k(null), h && p(v.current, e, x.current);
104
+ }, [
105
+ _,
106
+ h,
107
+ m,
108
+ a,
109
+ y
110
+ ]), O, k), [j, M] = r(!1);
111
+ t(() => {
112
+ M(!0);
113
+ }, []);
114
+ let N = p || j ? A : void 0, P = n(void 0);
115
+ return t(() => {
116
+ let e = a.getStorage();
117
+ if (!e) return;
118
+ let t = e.getItem(_);
119
+ if (t !== P.current && (P.current = t, t !== null)) try {
120
+ b.current.deserialize(t);
108
121
  } catch (e) {
109
- let t = e instanceof Error ? e : Error(String(e));
110
- console.warn(`[react-kit:use-local-storage] Failed to write key "${v.current}" to localStorage:`, t.message), k(t);
122
+ T(`Failed to read key "${_}"`, e instanceof Error ? e : Error(String(e)));
111
123
  }
112
- }, [h]), j = t(() => {
113
- let e = d();
114
- e && (e.removeItem(v.current), w(s), k(null), h && p(v.current, null, x.current));
115
- }, [h]);
116
- return e("storage", (e) => {
117
- if (g && e.storageArea === d() && e.key === v.current) {
118
- if (e.newValue === null) {
119
- w(s), k(null);
120
- return;
121
- }
124
+ }), {
125
+ value: N,
126
+ setValue: e((e) => {
127
+ let t = a.getStorage();
128
+ if (!t) return;
129
+ let n = O(), r = typeof e == "function" ? e(n) : e;
122
130
  try {
123
- let t = S.current.deserialize(e.newValue);
124
- w(t), k(null);
131
+ let e = b.current.serialize(r);
132
+ if (t.setItem(_, e), E.current = {
133
+ raw: e,
134
+ parsed: r
135
+ }, P.current = e, w(null), D.current?.(), m) {
136
+ let t = {
137
+ value: e,
138
+ instanceId: y
139
+ };
140
+ window.dispatchEvent(new CustomEvent(`${a.customEventName}:${_}`, { detail: t }));
141
+ }
125
142
  } catch (e) {
126
- let t = e instanceof Error ? e : Error(String(e));
127
- console.warn(`[react-kit:use-local-storage] Failed to deserialize cross-tab update for key "${v.current}":`, t.message), w(s), k(t);
128
- }
129
- }
130
- }), e(b, (e) => {
131
- if (!h) return;
132
- let t = e.detail;
133
- if (t.instanceId !== x.current) {
134
- if (t.value === null) {
135
- w(s), k(null);
136
- return;
143
+ T(`Failed to write key "${_}"`, e instanceof Error ? e : Error(String(e)));
137
144
  }
138
- try {
139
- let e = S.current.deserialize(t.value);
140
- w(e), k(null);
141
- } catch (e) {
142
- let t = e instanceof Error ? e : Error(String(e));
143
- console.warn(`[react-kit:use-local-storage] Failed to deserialize same-tab update for key "${v.current}":`, t.message), w(s), k(t);
145
+ }, [
146
+ a,
147
+ _,
148
+ m,
149
+ O,
150
+ T,
151
+ y
152
+ ]),
153
+ removeValue: e(() => {
154
+ let e = a.getStorage();
155
+ if (e && (e.removeItem(_), E.current = {
156
+ raw: null,
157
+ parsed: x.current
158
+ }, P.current = null, w(null), D.current?.(), m)) {
159
+ let e = {
160
+ value: null,
161
+ instanceId: y
162
+ };
163
+ window.dispatchEvent(new CustomEvent(`${a.customEventName}:${_}`, { detail: e }));
144
164
  }
145
- }
146
- }, { target: typeof window > "u" ? null : window }), {
147
- value: C,
148
- setValue: A,
149
- removeValue: j,
150
- isHydrated: E,
151
- error: O
165
+ }, [
166
+ a,
167
+ _,
168
+ m,
169
+ y
170
+ ]),
171
+ isHydrated: j,
172
+ error: C
152
173
  };
153
174
  }
154
175
  //#endregion
155
- //#region src/storage/useSessionStorage/constants.ts
156
- var h = "react-kit:use-session-storage";
157
- //#endregion
158
- //#region src/storage/useSessionStorage/utils.ts
159
- function g() {
160
- if (typeof window > "u") return null;
161
- try {
162
- return window.sessionStorage;
163
- } catch {
164
- return null;
165
- }
166
- }
167
- function _(e, t, n, r) {
168
- try {
169
- let i = e.getItem(t);
170
- return i === null ? {
171
- value: n,
172
- error: null
173
- } : {
174
- value: r.deserialize(i),
175
- error: null
176
- };
177
- } catch (e) {
178
- return {
179
- value: n,
180
- error: e instanceof Error ? e : Error(String(e))
181
- };
182
- }
183
- }
184
- function v(e, t, n) {
185
- let r = {
186
- value: t,
187
- instanceId: n
188
- }, i = new CustomEvent(`${h}:${e}`, { detail: r });
189
- window.dispatchEvent(i);
176
+ //#region src/storage/useLocalStorage/useLocalStorage.ts
177
+ var m = {
178
+ hookLabel: "react-kit:use-local-storage",
179
+ getStorage: a("localStorage"),
180
+ customEventName: "react-kit:use-local-storage",
181
+ nativeStorageEventSupported: !0
182
+ };
183
+ function h(e, t, n = {}) {
184
+ return p(m, e, t, n);
190
185
  }
191
186
  //#endregion
192
187
  //#region src/storage/useSessionStorage/useSessionStorage.ts
193
- function y(o, s, c = {}) {
194
- let { serializer: l = a, initializeWithValue: u = !0, sameInstanceSync: d = !0 } = c, [f] = i(() => o), p = r(f), m = o !== f, y = `${h}:${f}`, b = r(Symbol()), x = r(l);
195
- n(() => {
196
- x.current = l;
197
- }, [l]);
198
- let [S, C] = i(() => {
199
- if (!u) return;
200
- let e = g();
201
- if (!e) return s;
202
- let { value: t } = _(e, o, s, l);
203
- return t;
204
- }), w = r(S);
205
- n(() => {
206
- w.current = S;
207
- }, [S]);
208
- let [T, E] = i(u && typeof window < "u"), [D, O] = i(null);
209
- n(() => {
210
- if (!m) return;
211
- let e = /* @__PURE__ */ Error(`[react-kit:use-session-storage] Changing the storage key at runtime is not supported. Still using original key: "${p.current}". Received new key: "${o}".`);
212
- console.warn(e.message), O(e);
213
- }, [o, m]), n(() => {
214
- if (u) return;
215
- let e = g(), { value: t, error: n } = e === null ? {
216
- value: s,
217
- error: null
218
- } : _(e, p.current, s, x.current);
219
- n && (console.warn(`[react-kit:use-session-storage] Failed to read key "${p.current}" from sessionStorage:`, n.message), O(n)), C(t), E(!0);
220
- }, [u]);
221
- let k = t((e) => {
222
- let t = g();
223
- if (!t) return;
224
- let n = w.current, r = typeof e == "function" ? e(n) : e;
225
- try {
226
- let e = x.current.serialize(r);
227
- t.setItem(p.current, e), C(r), O(null), d && v(p.current, e, b.current);
228
- } catch (e) {
229
- let t = e instanceof Error ? e : Error(String(e));
230
- console.warn(`[react-kit:use-session-storage] Failed to write key "${p.current}" to sessionStorage:`, t.message), O(t);
231
- }
232
- }, [d]), A = t(() => {
233
- let e = g();
234
- e && (e.removeItem(p.current), C(s), O(null), d && v(p.current, null, b.current));
235
- }, [d]);
236
- return e(y, (e) => {
237
- if (!d) return;
238
- let t = e.detail;
239
- if (t.instanceId !== b.current) {
240
- if (t.value === null) {
241
- C(s), O(null);
242
- return;
243
- }
244
- try {
245
- let e = x.current.deserialize(t.value);
246
- C(e), O(null);
247
- } catch (e) {
248
- let t = e instanceof Error ? e : Error(String(e));
249
- console.warn(`[react-kit:use-session-storage] Failed to deserialize same-tab update for key "${p.current}":`, t.message), C(s), O(t);
250
- }
251
- }
252
- }, { target: typeof window > "u" ? null : window }), {
253
- value: S,
254
- setValue: k,
255
- removeValue: A,
256
- isHydrated: T,
257
- error: D
258
- };
188
+ var g = {
189
+ hookLabel: "react-kit:use-session-storage",
190
+ getStorage: a("sessionStorage"),
191
+ customEventName: "react-kit:use-session-storage",
192
+ nativeStorageEventSupported: !1
193
+ };
194
+ function _(e, t, n = {}) {
195
+ return p(g, e, t, n);
259
196
  }
260
197
  //#endregion
261
- export { a, c as i, m as n, o, l as r, s, y as t };
198
+ export { o as a, l as i, h as n, s as o, u as r, c as s, _ as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@himanshu-sorathiya/react-kit",
3
- "version": "1.0.26",
3
+ "version": "1.0.27",
4
4
  "description": "An opinionated collection of react hooks, and reusable UI components.",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",