use-storage-persisted-state 1.0.0
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/LICENSE +22 -0
- package/README.md +235 -0
- package/dist/index.d.mts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +478 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +446 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
MIT License
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 mikkoha
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# use-storage-persisted-state
|
|
2
|
+
|
|
3
|
+
A robust, type-safe React hook for persisting state backed by `localStorage`, `sessionStorage`, or memory.
|
|
4
|
+
|
|
5
|
+
`useStoragePersistedState` works like `useState`, but it automatically persists your state to the browser and keeps it synchronized across all components, tabs, and even direct localStorage changes, or manual changes in DevTools.
|
|
6
|
+
|
|
7
|
+
## Features (Why another storage hook?)
|
|
8
|
+
|
|
9
|
+
- **Type safety**: Full TypeScript type inference and safety.
|
|
10
|
+
- **Sync between components**: Keeps state synchronized across all components using the same key.
|
|
11
|
+
- **Cross-tab sync**: Automatically synchronizes state across tabs (using native `StorageEvent`).
|
|
12
|
+
- **External change detection**: Detects changes made directly to storage (e.g., via DevTools or `window.localStorage.setItem`) using (optional) polling.
|
|
13
|
+
- **SSR ready**: Safe for Server-Side Rendering (e.g., Next.js) using proper hydration techniques (React `useSyncExternalStore` with a shim for React 16.8+ support).
|
|
14
|
+
- **Custom serialization**: Supports custom serializer implementation for advanced use cases like data schema migration.
|
|
15
|
+
- **Graceful error handling**: Automatically falls back to in-memory storage if `QuotaExceededError` occurs or storage is unavailable.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install use-storage-persisted-state
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### 1. Basic usage
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import { useStoragePersistedState } from "use-storage-persisted-state";
|
|
29
|
+
|
|
30
|
+
function Counter() {
|
|
31
|
+
const [count, setCount] = useStoragePersistedState("count", 0);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<button onClick={() => setCount((prev) => prev + 1)}>Count: {count}</button>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Basic sync example
|
|
40
|
+
|
|
41
|
+
Any component using the same key will stay in sync, even across different tabs. The state survives page reloads, because it is stored in `localStorage` (default).
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useStoragePersistedState } from "use-storage-persisted-state";
|
|
45
|
+
|
|
46
|
+
function ComponentA() {
|
|
47
|
+
const [username, setUsername] = useStoragePersistedState(
|
|
48
|
+
"user_name",
|
|
49
|
+
"Guest",
|
|
50
|
+
);
|
|
51
|
+
return (
|
|
52
|
+
<input value={username} onChange={(e) => setUsername(e.target.value)} />
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ComponentB() {
|
|
57
|
+
const [username] = useStoragePersistedState("user_name", "Guest");
|
|
58
|
+
return <p>Hello, {username}!</p>;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Explicit codec (undefined default value)
|
|
63
|
+
|
|
64
|
+
If your default value is `undefined` or `null`, you must provide an explicit codec so the hook knows how to serialize/deserialize the data.
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
import {
|
|
68
|
+
useStoragePersistedState,
|
|
69
|
+
StringCodec,
|
|
70
|
+
} from "use-storage-persisted-state";
|
|
71
|
+
|
|
72
|
+
function FavoriteColor() {
|
|
73
|
+
// We use StringCodec explicitly since defaultValue is undefined and Codec cannot be inferred.
|
|
74
|
+
const [color, setColor] = useStoragePersistedState<string | undefined>(
|
|
75
|
+
"favorite_color",
|
|
76
|
+
undefined,
|
|
77
|
+
{ codec: StringCodec },
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<input
|
|
82
|
+
value={color ?? ""}
|
|
83
|
+
onChange={(e) => setColor(e.target.value || undefined)}
|
|
84
|
+
placeholder="Enter your favorite color"
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import {
|
|
92
|
+
useStoragePersistedState,
|
|
93
|
+
JsonCodec,
|
|
94
|
+
} from "use-storage-persisted-state";
|
|
95
|
+
|
|
96
|
+
function UserProfile() {
|
|
97
|
+
// We use JsonCodec explicitly since Codec inference from 'null' is ambiguous (could be string | null, number | null, etc.)
|
|
98
|
+
const [user, setUser] = useStoragePersistedState<{ name: string } | null>(
|
|
99
|
+
"user_profile",
|
|
100
|
+
null,
|
|
101
|
+
{ codec: JsonCodec },
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
if (!user)
|
|
105
|
+
return <button onClick={() => setUser({ name: "Alice" })}>Login</button>;
|
|
106
|
+
|
|
107
|
+
return <div>Welcome, {user.name}</div>;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
By default, the codec is inferred from the type of `defaultValue` if possible.
|
|
112
|
+
|
|
113
|
+
- If `defaultValue` is a primitive type (string, number, boolean), the value is stored as a simple string (with StringCodec, NumberCodec, or BooleanCodec respectively).
|
|
114
|
+
- If `defaultValue` is an object or array, a built-in `JsonCodec` is used by default.
|
|
115
|
+
- There is nothing magical about codecs; they are just objects with `encode` (e.g., `JSON.stringify`) and `decode` (e.g., `JSON.parse`) methods. You can provide your own codec for custom serialization logic.
|
|
116
|
+
|
|
117
|
+
### 4. Read and write outside React
|
|
118
|
+
|
|
119
|
+
You can read and write values without using the hook. These utilities still parse via codecs and notify active hooks using the same key. You can, of course, also use window.localStorage/sessionStorage directly, but then you have to handle serialization and hook notifications yourself (if you're not using polling or want immediate updates).
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import {
|
|
123
|
+
readStoragePersistedState,
|
|
124
|
+
setStoragePersistedState,
|
|
125
|
+
JsonCodec,
|
|
126
|
+
} from "use-storage-persisted-state";
|
|
127
|
+
|
|
128
|
+
// Read a number value with inferred NumberCodec.
|
|
129
|
+
const count = readStoragePersistedState("count", 0);
|
|
130
|
+
|
|
131
|
+
// Explicit codec is required since the default value is null.
|
|
132
|
+
const user = readStoragePersistedState<{ name: string } | null>(
|
|
133
|
+
"user_profile",
|
|
134
|
+
null,
|
|
135
|
+
{ codec: JsonCodec },
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Write an object value with inferred JsonCodec.
|
|
139
|
+
setStoragePersistedState("user_profile", { name: "Alice" });
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Advanced usage
|
|
143
|
+
|
|
144
|
+
### Data schema migration with custom codec
|
|
145
|
+
|
|
146
|
+
You can handle schema migrations (e.g., renaming fields) by creating a custom codec.
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import {
|
|
150
|
+
useStoragePersistedState,
|
|
151
|
+
Codec,
|
|
152
|
+
JsonCodec,
|
|
153
|
+
} from "use-storage-persisted-state";
|
|
154
|
+
|
|
155
|
+
interface OldSettings {
|
|
156
|
+
darkMode: boolean;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
interface NewSettings {
|
|
160
|
+
theme: "dark" | "light";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const SettingsCodec: Codec<NewSettings> = {
|
|
164
|
+
encode: (value) => JSON.stringify(value),
|
|
165
|
+
decode: (value) => {
|
|
166
|
+
if (value === null) return { theme: "light" };
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const parsed = JSON.parse(value);
|
|
170
|
+
|
|
171
|
+
// Migration logic: convert old boolean to new string enum
|
|
172
|
+
if ("darkMode" in parsed) {
|
|
173
|
+
return { theme: parsed.darkMode ? "dark" : "light" };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return parsed;
|
|
177
|
+
} catch {
|
|
178
|
+
return { theme: "light" };
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
function Settings() {
|
|
184
|
+
const [settings, setSettings] = useStoragePersistedState<NewSettings>(
|
|
185
|
+
"app_settings",
|
|
186
|
+
{ theme: "light" },
|
|
187
|
+
{ codec: SettingsCodec },
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
return <div>Current Theme: {settings.theme}</div>;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Options
|
|
195
|
+
|
|
196
|
+
`useStoragePersistedState(key, defaultValue, options)`
|
|
197
|
+
|
|
198
|
+
- `key: string` - The storage key to be used with `localStorage`, `sessionStorage`, or `memory` storage.
|
|
199
|
+
- `defaultValue: T` - The default value to use if there is no value in storage. Note: this is not just the initial value; it is returned whenever the stored value is missing (e.g., after removal or a read error).
|
|
200
|
+
- `options?: StoragePersistedStateOptions<T>` - Optional configuration object. See table below.
|
|
201
|
+
|
|
202
|
+
| Option | Type | Default | Description |
|
|
203
|
+
| ------------------- | ---------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
204
|
+
| `codec` | `Codec<T>` | Inferred | Defines how to encode/decode values. Required if `defaultValue` is `null`/`undefined`. |
|
|
205
|
+
| `storageType` | `'localStorage'` \| `'sessionStorage'` \| `'memory'` | `'localStorage'` | Which storage backend to use. `memory` is a simple in-memory storage that does not persist across reloads. |
|
|
206
|
+
| `crossTabSync` | `boolean` | `true` | Enables syncing between tabs via listening to native `StorageEvent`. |
|
|
207
|
+
| `pollingIntervalMs` | `number` \| `null` | `2000` | Polling interval (milliseconds) to detect changes made outside this hook (e.g. devtools). Set `null` to disable polling. |
|
|
208
|
+
|
|
209
|
+
## FAQ
|
|
210
|
+
|
|
211
|
+
### How is `QuotaExceededError` handled?
|
|
212
|
+
|
|
213
|
+
If `localStorage` or `sessionStorage` is full, writing to it will typically throw a `QuotaExceededError`. This library handles this gracefully by catching the error and automatically falling back to an in-memory storage for that specific key. This means your application won't crash, and the state will persist for the session (until page reload), even if it couldn't be persisted.
|
|
214
|
+
|
|
215
|
+
### How is this different from other storage hooks?
|
|
216
|
+
|
|
217
|
+
This package shares similarities with, for example:
|
|
218
|
+
|
|
219
|
+
- `use-storage-state`
|
|
220
|
+
- `usehooks-ts` (`useLocalStorage`)
|
|
221
|
+
- `use-local-storage-state`
|
|
222
|
+
|
|
223
|
+
Key differences include:
|
|
224
|
+
|
|
225
|
+
- built-in or custom serialize/deserialize support that saves by default primitive types as simple strings, and objects and arrays as JSON
|
|
226
|
+
- automatic in-memory fallback (or, can be used as a memory-only synced state hook)
|
|
227
|
+
- robust sync behavior with optional polling for catching all external changes to underlying storage
|
|
228
|
+
- full TypeScript type inference and safety
|
|
229
|
+
- SSR ready with proper hydration using `useSyncExternalStore` (with React 16.8+ support via shim)
|
|
230
|
+
- handles edge-cases like `QuotaExceededError`, and other storage unavailability
|
|
231
|
+
- provides read/write utilities for use where hooks cannot be used, while maintaining sync and serialization
|
|
232
|
+
|
|
233
|
+
### How does the hook handle null and undefined values?
|
|
234
|
+
|
|
235
|
+
When the state is set to `null` or `undefined`, the hook will remove the corresponding item from the underlying storage (`localStorage`/`sessionStorage`). This means that subsequent reads will return the `defaultValue` provided to the hook until an explicit value is set.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines how to serialize/deserialize a value from localStorage.
|
|
3
|
+
* null means the key doesn't exist.
|
|
4
|
+
*/
|
|
5
|
+
interface Codec<T> {
|
|
6
|
+
encode: (value: T) => string | null;
|
|
7
|
+
decode: (value: string | null) => T;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A robust JSON codec that handles parsing errors gracefully.
|
|
11
|
+
* Works with objects, arrays, and other JSON-serializable values.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const [user, setUser] = useStoragePersistedState<User | null>(
|
|
16
|
+
* "user",
|
|
17
|
+
* null,
|
|
18
|
+
* { codec: JsonCodec }
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const JsonCodec: Codec<unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Codec for string values. Stores strings as-is without any transformation.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const [name, setName] = useStoragePersistedState("name", "Guest");
|
|
29
|
+
* // Automatically uses StringCodec because defaultValue is a string
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare const StringCodec: Codec<string | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Codec for boolean values. Stores as "true" or "false" strings.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const [isDark, setIsDark] = useStoragePersistedState("darkMode", false);
|
|
39
|
+
* // Automatically uses BooleanCodec because defaultValue is a boolean
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare const BooleanCodec: Codec<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Codec for number values. Handles NaN, Infinity, and -Infinity correctly.
|
|
45
|
+
* Returns null for unparseable values (e.g., empty string, invalid number string).
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const [count, setCount] = useStoragePersistedState("count", 0);
|
|
50
|
+
* // Automatically uses NumberCodec because defaultValue is a number
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare const NumberCodec: Codec<number | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Infers the appropriate codec based on the default value's type.
|
|
56
|
+
* Used internally when the user doesn't provide an explicit codec.
|
|
57
|
+
*
|
|
58
|
+
* - `boolean` → BooleanCodec
|
|
59
|
+
* - `number` → NumberCodec
|
|
60
|
+
* - `string` → StringCodec
|
|
61
|
+
* - `object`, `array`, `undefined`, `null` → JsonCodec
|
|
62
|
+
*
|
|
63
|
+
* @param defaultValue - The default value to infer the codec from
|
|
64
|
+
* @returns The inferred codec for the given value type
|
|
65
|
+
*/
|
|
66
|
+
declare function inferCodec<T>(defaultValue: T): Codec<T>;
|
|
67
|
+
|
|
68
|
+
type StorageType = "localStorage" | "sessionStorage" | "memory";
|
|
69
|
+
/**
|
|
70
|
+
* Options for the useStoragePersistedState hook
|
|
71
|
+
*/
|
|
72
|
+
interface StoragePersistedStateOptions<T> {
|
|
73
|
+
/**
|
|
74
|
+
* Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).
|
|
75
|
+
* If not provided, codec is inferred from defaultValue type.
|
|
76
|
+
*/
|
|
77
|
+
codec?: Codec<T>;
|
|
78
|
+
/**
|
|
79
|
+
* Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.
|
|
80
|
+
*/
|
|
81
|
+
storageType?: StorageType;
|
|
82
|
+
/**
|
|
83
|
+
* Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.
|
|
84
|
+
* defaults to true.
|
|
85
|
+
*/
|
|
86
|
+
crossTabSync?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.
|
|
89
|
+
* defaults to 2000ms.
|
|
90
|
+
*/
|
|
91
|
+
pollingIntervalMs?: number | null;
|
|
92
|
+
}
|
|
93
|
+
declare function useStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): [T, (newValue: T | ((prev: T) => T)) => void, () => void];
|
|
94
|
+
declare function useStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
|
|
95
|
+
codec: Codec<T>;
|
|
96
|
+
}): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Read a persisted value from storage using the same codec behavior as the hook.
|
|
100
|
+
*
|
|
101
|
+
* Returns the provided defaultValue when the key is missing or parsing fails.
|
|
102
|
+
*/
|
|
103
|
+
declare function readStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): T;
|
|
104
|
+
/**
|
|
105
|
+
* Read a persisted value from storage using an explicit codec.
|
|
106
|
+
*
|
|
107
|
+
* Use this overload when defaultValue is null or undefined.
|
|
108
|
+
*/
|
|
109
|
+
declare function readStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
|
|
110
|
+
codec: Codec<T>;
|
|
111
|
+
}): T | null;
|
|
112
|
+
/**
|
|
113
|
+
* Set a persisted value in storage and notify active hooks for the same key.
|
|
114
|
+
*
|
|
115
|
+
* Supports functional updates using the current decoded value.
|
|
116
|
+
*/
|
|
117
|
+
declare function setStoragePersistedState<T>(key: string, newValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): void;
|
|
118
|
+
/**
|
|
119
|
+
* Set a persisted value in storage using an explicit codec and notify listeners.
|
|
120
|
+
*
|
|
121
|
+
* Use this overload when the new value is null or undefined or when you want custom serialization.
|
|
122
|
+
*/
|
|
123
|
+
declare function setStoragePersistedState<T>(key: string, newValue: T | ((prev: T | null) => T), options: StoragePersistedStateOptions<T> & {
|
|
124
|
+
codec: Codec<T>;
|
|
125
|
+
}): void;
|
|
126
|
+
|
|
127
|
+
export { BooleanCodec, type Codec, JsonCodec, NumberCodec, type StoragePersistedStateOptions, type StorageType, StringCodec, inferCodec, readStoragePersistedState, setStoragePersistedState, useStoragePersistedState };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines how to serialize/deserialize a value from localStorage.
|
|
3
|
+
* null means the key doesn't exist.
|
|
4
|
+
*/
|
|
5
|
+
interface Codec<T> {
|
|
6
|
+
encode: (value: T) => string | null;
|
|
7
|
+
decode: (value: string | null) => T;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A robust JSON codec that handles parsing errors gracefully.
|
|
11
|
+
* Works with objects, arrays, and other JSON-serializable values.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* const [user, setUser] = useStoragePersistedState<User | null>(
|
|
16
|
+
* "user",
|
|
17
|
+
* null,
|
|
18
|
+
* { codec: JsonCodec }
|
|
19
|
+
* );
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const JsonCodec: Codec<unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Codec for string values. Stores strings as-is without any transformation.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const [name, setName] = useStoragePersistedState("name", "Guest");
|
|
29
|
+
* // Automatically uses StringCodec because defaultValue is a string
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare const StringCodec: Codec<string | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Codec for boolean values. Stores as "true" or "false" strings.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const [isDark, setIsDark] = useStoragePersistedState("darkMode", false);
|
|
39
|
+
* // Automatically uses BooleanCodec because defaultValue is a boolean
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare const BooleanCodec: Codec<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Codec for number values. Handles NaN, Infinity, and -Infinity correctly.
|
|
45
|
+
* Returns null for unparseable values (e.g., empty string, invalid number string).
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const [count, setCount] = useStoragePersistedState("count", 0);
|
|
50
|
+
* // Automatically uses NumberCodec because defaultValue is a number
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare const NumberCodec: Codec<number | null>;
|
|
54
|
+
/**
|
|
55
|
+
* Infers the appropriate codec based on the default value's type.
|
|
56
|
+
* Used internally when the user doesn't provide an explicit codec.
|
|
57
|
+
*
|
|
58
|
+
* - `boolean` → BooleanCodec
|
|
59
|
+
* - `number` → NumberCodec
|
|
60
|
+
* - `string` → StringCodec
|
|
61
|
+
* - `object`, `array`, `undefined`, `null` → JsonCodec
|
|
62
|
+
*
|
|
63
|
+
* @param defaultValue - The default value to infer the codec from
|
|
64
|
+
* @returns The inferred codec for the given value type
|
|
65
|
+
*/
|
|
66
|
+
declare function inferCodec<T>(defaultValue: T): Codec<T>;
|
|
67
|
+
|
|
68
|
+
type StorageType = "localStorage" | "sessionStorage" | "memory";
|
|
69
|
+
/**
|
|
70
|
+
* Options for the useStoragePersistedState hook
|
|
71
|
+
*/
|
|
72
|
+
interface StoragePersistedStateOptions<T> {
|
|
73
|
+
/**
|
|
74
|
+
* Explicit codec for when defaultValue is null or undefined, for complex types, or special cases (e.g., data migration on read).
|
|
75
|
+
* If not provided, codec is inferred from defaultValue type.
|
|
76
|
+
*/
|
|
77
|
+
codec?: Codec<T>;
|
|
78
|
+
/**
|
|
79
|
+
* Storage type to use: 'localStorage' (default), 'sessionStorage', or 'memory'.
|
|
80
|
+
*/
|
|
81
|
+
storageType?: StorageType;
|
|
82
|
+
/**
|
|
83
|
+
* Enable cross-tab synchronization via the StorageEvent. Note: disabling this will not stop other tabs from updating localStorage, but this hook will not automatically respond to those changes.
|
|
84
|
+
* defaults to true.
|
|
85
|
+
*/
|
|
86
|
+
crossTabSync?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Polling interval in milliseconds for detecting storage changes made outside of React (e.g., DevTools, direct localStorage manipulation). Set to null to disable polling.
|
|
89
|
+
* defaults to 2000ms.
|
|
90
|
+
*/
|
|
91
|
+
pollingIntervalMs?: number | null;
|
|
92
|
+
}
|
|
93
|
+
declare function useStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): [T, (newValue: T | ((prev: T) => T)) => void, () => void];
|
|
94
|
+
declare function useStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
|
|
95
|
+
codec: Codec<T>;
|
|
96
|
+
}): [T | null, (newValue: T | ((prev: T) => T)) => void, () => void];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Read a persisted value from storage using the same codec behavior as the hook.
|
|
100
|
+
*
|
|
101
|
+
* Returns the provided defaultValue when the key is missing or parsing fails.
|
|
102
|
+
*/
|
|
103
|
+
declare function readStoragePersistedState<T>(key: string, defaultValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): T;
|
|
104
|
+
/**
|
|
105
|
+
* Read a persisted value from storage using an explicit codec.
|
|
106
|
+
*
|
|
107
|
+
* Use this overload when defaultValue is null or undefined.
|
|
108
|
+
*/
|
|
109
|
+
declare function readStoragePersistedState<T>(key: string, defaultValue: null | undefined, options: StoragePersistedStateOptions<T> & {
|
|
110
|
+
codec: Codec<T>;
|
|
111
|
+
}): T | null;
|
|
112
|
+
/**
|
|
113
|
+
* Set a persisted value in storage and notify active hooks for the same key.
|
|
114
|
+
*
|
|
115
|
+
* Supports functional updates using the current decoded value.
|
|
116
|
+
*/
|
|
117
|
+
declare function setStoragePersistedState<T>(key: string, newValue: Exclude<T, null | undefined>, options?: StoragePersistedStateOptions<T>): void;
|
|
118
|
+
/**
|
|
119
|
+
* Set a persisted value in storage using an explicit codec and notify listeners.
|
|
120
|
+
*
|
|
121
|
+
* Use this overload when the new value is null or undefined or when you want custom serialization.
|
|
122
|
+
*/
|
|
123
|
+
declare function setStoragePersistedState<T>(key: string, newValue: T | ((prev: T | null) => T), options: StoragePersistedStateOptions<T> & {
|
|
124
|
+
codec: Codec<T>;
|
|
125
|
+
}): void;
|
|
126
|
+
|
|
127
|
+
export { BooleanCodec, type Codec, JsonCodec, NumberCodec, type StoragePersistedStateOptions, type StorageType, StringCodec, inferCodec, readStoragePersistedState, setStoragePersistedState, useStoragePersistedState };
|