@usefy/use-init 0.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +610 -0
- package/dist/index.d.mts +100 -0
- package/dist/index.d.ts +100 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +139 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/geon0529/usefy/master/assets/logo.png" alt="usefy logo" width="120" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@usefy/use-init</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>A powerful React hook for one-time initialization with async support, retry, timeout, and conditional execution</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/@usefy/use-init">
|
|
13
|
+
<img src="https://img.shields.io/npm/v/@usefy/use-init.svg?style=flat-square&color=007acc" alt="npm version" />
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://www.npmjs.com/package/@usefy/use-init">
|
|
16
|
+
<img src="https://img.shields.io/npm/dm/@usefy/use-init.svg?style=flat-square&color=007acc" alt="npm downloads" />
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://bundlephobia.com/package/@usefy/use-init">
|
|
19
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@usefy/use-init?style=flat-square&color=007acc" alt="bundle size" />
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://github.com/geon0529/usefy/blob/master/LICENSE">
|
|
22
|
+
<img src="https://img.shields.io/npm/l/@usefy/use-init.svg?style=flat-square&color=007acc" alt="license" />
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<a href="#installation">Installation</a> •
|
|
28
|
+
<a href="#quick-start">Quick Start</a> •
|
|
29
|
+
<a href="#api-reference">API Reference</a> •
|
|
30
|
+
<a href="#examples">Examples</a> •
|
|
31
|
+
<a href="#license">License</a>
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<a href="https://geon0529.github.io/usefy/?path=/docs/hooks-useinit--docs" target="_blank" rel="noopener noreferrer">
|
|
36
|
+
<strong>📚 View Storybook Demo</strong>
|
|
37
|
+
</a>
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Overview
|
|
43
|
+
|
|
44
|
+
`@usefy/use-init` is a React hook for executing initialization logic exactly once when a component mounts. It supports synchronous and asynchronous callbacks, automatic retry on failure, timeout handling, conditional execution, and cleanup functions. Perfect for initializing services, loading configuration, setting up subscriptions, and any one-time setup tasks.
|
|
45
|
+
|
|
46
|
+
**Part of the [@usefy](https://www.npmjs.com/org/usefy) ecosystem** — a collection of production-ready React hooks designed for modern applications.
|
|
47
|
+
|
|
48
|
+
### Why use-init?
|
|
49
|
+
|
|
50
|
+
- **Zero Dependencies** — Pure React implementation with no external dependencies
|
|
51
|
+
- **TypeScript First** — Full type safety with exported interfaces
|
|
52
|
+
- **One-Time Execution** — Guarantees initialization runs only once per mount
|
|
53
|
+
- **Async Support** — Handles both synchronous and asynchronous initialization callbacks
|
|
54
|
+
- **Cleanup Functions** — Optional cleanup function support for resource management
|
|
55
|
+
- **Retry Logic** — Automatic retry with configurable attempts and delays
|
|
56
|
+
- **Timeout Handling** — Built-in timeout support with custom error handling
|
|
57
|
+
- **Conditional Execution** — Run initialization only when conditions are met
|
|
58
|
+
- **State Tracking** — Track initialization status, loading state, and errors
|
|
59
|
+
- **Manual Reinitialize** — Trigger re-initialization programmatically
|
|
60
|
+
- **SSR Compatible** — Works seamlessly with Next.js, Remix, and other SSR frameworks
|
|
61
|
+
- **Well Tested** — Comprehensive test coverage with Vitest
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# npm
|
|
69
|
+
npm install @usefy/use-init
|
|
70
|
+
|
|
71
|
+
# yarn
|
|
72
|
+
yarn add @usefy/use-init
|
|
73
|
+
|
|
74
|
+
# pnpm
|
|
75
|
+
pnpm add @usefy/use-init
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Peer Dependencies
|
|
79
|
+
|
|
80
|
+
This package requires React 18 or 19:
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"peerDependencies": {
|
|
85
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Quick Start
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { useInit } from "@usefy/use-init";
|
|
96
|
+
|
|
97
|
+
function MyComponent() {
|
|
98
|
+
const { isInitialized, isInitializing, error } = useInit(async () => {
|
|
99
|
+
await loadConfiguration();
|
|
100
|
+
console.log("Component initialized!");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (isInitializing) return <div>Loading...</div>;
|
|
104
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
105
|
+
if (!isInitialized) return null;
|
|
106
|
+
|
|
107
|
+
return <div>Ready!</div>;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## API Reference
|
|
114
|
+
|
|
115
|
+
### `useInit(callback, options?)`
|
|
116
|
+
|
|
117
|
+
A hook that executes initialization logic exactly once when the component mounts (or when conditions are met).
|
|
118
|
+
|
|
119
|
+
#### Parameters
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Default | Description |
|
|
122
|
+
| ---------- | ---------------- | ------- | ---------------------------------------- |
|
|
123
|
+
| `callback` | `InitCallback` | — | The initialization function to run |
|
|
124
|
+
| `options` | `UseInitOptions` | `{}` | Configuration options for initialization |
|
|
125
|
+
|
|
126
|
+
#### Callback Type
|
|
127
|
+
|
|
128
|
+
The callback can be:
|
|
129
|
+
|
|
130
|
+
- **Synchronous**: `() => void`
|
|
131
|
+
- **Asynchronous**: `() => Promise<void>`
|
|
132
|
+
- **With cleanup**: `() => void | CleanupFn` or `() => Promise<void | CleanupFn>`
|
|
133
|
+
|
|
134
|
+
Where `CleanupFn` is `() => void` - a function that will be called when the component unmounts or before re-initialization.
|
|
135
|
+
|
|
136
|
+
#### Options
|
|
137
|
+
|
|
138
|
+
| Option | Type | Default | Description |
|
|
139
|
+
| ------------ | --------- | ------- | --------------------------------------------------- |
|
|
140
|
+
| `when` | `boolean` | `true` | Only run initialization when this condition is true |
|
|
141
|
+
| `retry` | `number` | `0` | Number of retry attempts on failure |
|
|
142
|
+
| `retryDelay` | `number` | `1000` | Delay between retry attempts in milliseconds |
|
|
143
|
+
| `timeout` | `number` | — | Timeout for initialization in milliseconds |
|
|
144
|
+
|
|
145
|
+
#### Returns `UseInitResult`
|
|
146
|
+
|
|
147
|
+
| Property | Type | Description |
|
|
148
|
+
| ---------------- | --------------- | -------------------------------------------------------------- |
|
|
149
|
+
| `isInitialized` | `boolean` | Whether initialization has completed successfully |
|
|
150
|
+
| `isInitializing` | `boolean` | Whether initialization is currently in progress |
|
|
151
|
+
| `error` | `Error \| null` | Error that occurred during initialization, if any |
|
|
152
|
+
| `reinitialize` | `() => void` | Manually trigger re-initialization (respects `when` condition) |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Examples
|
|
157
|
+
|
|
158
|
+
### Basic Synchronous Initialization
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
import { useInit } from "@usefy/use-init";
|
|
162
|
+
|
|
163
|
+
function BasicComponent() {
|
|
164
|
+
useInit(() => {
|
|
165
|
+
console.log("Component initialized!");
|
|
166
|
+
initializeAnalytics();
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return <div>My Component</div>;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Async Initialization with Status Tracking
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
import { useInit } from "@usefy/use-init";
|
|
177
|
+
|
|
178
|
+
function DataLoader() {
|
|
179
|
+
const [data, setData] = useState(null);
|
|
180
|
+
const { isInitialized, isInitializing, error } = useInit(async () => {
|
|
181
|
+
const response = await fetch("/api/data");
|
|
182
|
+
const result = await response.json();
|
|
183
|
+
setData(result);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (isInitializing) return <div>Loading data...</div>;
|
|
187
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
188
|
+
if (!isInitialized) return null;
|
|
189
|
+
|
|
190
|
+
return <div>{JSON.stringify(data)}</div>;
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### With Cleanup Function
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
import { useInit } from "@usefy/use-init";
|
|
198
|
+
|
|
199
|
+
function SubscriptionComponent() {
|
|
200
|
+
useInit(() => {
|
|
201
|
+
const subscription = eventBus.subscribe("event", handleEvent);
|
|
202
|
+
|
|
203
|
+
// Return cleanup function
|
|
204
|
+
return () => {
|
|
205
|
+
subscription.unsubscribe();
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return <div>Subscribed to events</div>;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Conditional Initialization
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import { useInit } from "@usefy/use-init";
|
|
217
|
+
|
|
218
|
+
function ConditionalComponent({ isEnabled }: { isEnabled: boolean }) {
|
|
219
|
+
const { isInitialized } = useInit(
|
|
220
|
+
() => {
|
|
221
|
+
initializeFeature();
|
|
222
|
+
},
|
|
223
|
+
{ when: isEnabled }
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (!isEnabled) return <div>Feature disabled</div>;
|
|
227
|
+
if (!isInitialized) return <div>Initializing...</div>;
|
|
228
|
+
|
|
229
|
+
return <div>Feature ready!</div>;
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### With Retry Logic
|
|
234
|
+
|
|
235
|
+
```tsx
|
|
236
|
+
import { useInit } from "@usefy/use-init";
|
|
237
|
+
|
|
238
|
+
function ResilientComponent() {
|
|
239
|
+
const { isInitialized, error, reinitialize } = useInit(
|
|
240
|
+
async () => {
|
|
241
|
+
await connectToServer();
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
retry: 3,
|
|
245
|
+
retryDelay: 1000, // Wait 1 second between retries
|
|
246
|
+
}
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (error) {
|
|
250
|
+
return (
|
|
251
|
+
<div>
|
|
252
|
+
<p>Failed to connect: {error.message}</p>
|
|
253
|
+
<button onClick={reinitialize}>Retry</button>
|
|
254
|
+
</div>
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!isInitialized) return <div>Connecting...</div>;
|
|
259
|
+
|
|
260
|
+
return <div>Connected!</div>;
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### With Timeout
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
import { useInit } from "@usefy/use-init";
|
|
268
|
+
|
|
269
|
+
function TimeoutComponent() {
|
|
270
|
+
const { isInitialized, error } = useInit(
|
|
271
|
+
async () => {
|
|
272
|
+
await slowOperation();
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
timeout: 5000, // Fail after 5 seconds
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
if (error) {
|
|
280
|
+
return <div>Timeout: {error.message}</div>;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (!isInitialized) return <div>Processing...</div>;
|
|
284
|
+
|
|
285
|
+
return <div>Completed!</div>;
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Combined Options: Retry + Timeout + Conditional
|
|
290
|
+
|
|
291
|
+
```tsx
|
|
292
|
+
import { useInit } from "@usefy/use-init";
|
|
293
|
+
|
|
294
|
+
function AdvancedComponent({ shouldInit }: { shouldInit: boolean }) {
|
|
295
|
+
const { isInitialized, isInitializing, error, reinitialize } = useInit(
|
|
296
|
+
async () => {
|
|
297
|
+
await initializeService();
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
when: shouldInit,
|
|
301
|
+
retry: 2,
|
|
302
|
+
retryDelay: 2000,
|
|
303
|
+
timeout: 10000,
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
if (!shouldInit) return <div>Waiting for condition...</div>;
|
|
308
|
+
if (isInitializing) return <div>Initializing (attempt in progress)...</div>;
|
|
309
|
+
if (error) {
|
|
310
|
+
return (
|
|
311
|
+
<div>
|
|
312
|
+
<p>Error: {error.message}</p>
|
|
313
|
+
<button onClick={reinitialize}>Try Again</button>
|
|
314
|
+
</div>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (!isInitialized) return <div>Not initialized</div>;
|
|
318
|
+
|
|
319
|
+
return <div>Service initialized successfully!</div>;
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Manual Re-initialization
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
import { useInit } from "@usefy/use-init";
|
|
327
|
+
|
|
328
|
+
function RefreshableComponent() {
|
|
329
|
+
const [refreshKey, setRefreshKey] = useState(0);
|
|
330
|
+
const { isInitialized, reinitialize } = useInit(async () => {
|
|
331
|
+
await loadData();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
const handleRefresh = () => {
|
|
335
|
+
setRefreshKey((k) => k + 1);
|
|
336
|
+
reinitialize();
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div>
|
|
341
|
+
<button onClick={handleRefresh}>Refresh Data</button>
|
|
342
|
+
{isInitialized && <div>Data loaded (key: {refreshKey})</div>}
|
|
343
|
+
</div>
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Async Cleanup Function
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
import { useInit } from "@usefy/use-init";
|
|
352
|
+
|
|
353
|
+
function AsyncCleanupComponent() {
|
|
354
|
+
useInit(async () => {
|
|
355
|
+
const connection = await establishConnection();
|
|
356
|
+
|
|
357
|
+
// Return async cleanup function
|
|
358
|
+
return async () => {
|
|
359
|
+
await connection.close();
|
|
360
|
+
console.log("Connection closed");
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return <div>Connected</div>;
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Initializing Multiple Services
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
import { useInit } from "@usefy/use-init";
|
|
372
|
+
|
|
373
|
+
function MultiServiceComponent() {
|
|
374
|
+
const analytics = useInit(() => {
|
|
375
|
+
initializeAnalytics();
|
|
376
|
+
return () => analyticsService.shutdown();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const logging = useInit(async () => {
|
|
380
|
+
await initializeLogging();
|
|
381
|
+
return () => loggingService.disconnect();
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const config = useInit(async () => {
|
|
385
|
+
const config = await loadConfig();
|
|
386
|
+
return config;
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const allReady =
|
|
390
|
+
analytics.isInitialized && logging.isInitialized && config.isInitialized;
|
|
391
|
+
|
|
392
|
+
if (!allReady) return <div>Initializing services...</div>;
|
|
393
|
+
|
|
394
|
+
return <div>All services ready!</div>;
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## TypeScript
|
|
401
|
+
|
|
402
|
+
This hook is written in TypeScript with full type safety.
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import {
|
|
406
|
+
useInit,
|
|
407
|
+
type UseInitOptions,
|
|
408
|
+
type UseInitResult,
|
|
409
|
+
} from "@usefy/use-init";
|
|
410
|
+
|
|
411
|
+
// Basic usage with type inference
|
|
412
|
+
const { isInitialized } = useInit(() => {
|
|
413
|
+
console.log("Init");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// With options
|
|
417
|
+
const options: UseInitOptions = {
|
|
418
|
+
when: true,
|
|
419
|
+
retry: 3,
|
|
420
|
+
retryDelay: 1000,
|
|
421
|
+
timeout: 5000,
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const result: UseInitResult = useInit(async () => {
|
|
425
|
+
await initialize();
|
|
426
|
+
}, options);
|
|
427
|
+
|
|
428
|
+
// Cleanup function types
|
|
429
|
+
useInit(() => {
|
|
430
|
+
const resource = createResource();
|
|
431
|
+
return () => {
|
|
432
|
+
// TypeScript knows this is a cleanup function
|
|
433
|
+
resource.cleanup();
|
|
434
|
+
};
|
|
435
|
+
});
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Behavior Details
|
|
441
|
+
|
|
442
|
+
### One-Time Execution
|
|
443
|
+
|
|
444
|
+
The hook guarantees that initialization runs only once per component mount. Even if the `callback` reference changes, initialization will not run again unless:
|
|
445
|
+
|
|
446
|
+
- The component unmounts and remounts
|
|
447
|
+
- `reinitialize()` is called manually
|
|
448
|
+
- The `when` condition changes from `false` to `true` (after initial mount)
|
|
449
|
+
|
|
450
|
+
### Conditional Execution (`when`)
|
|
451
|
+
|
|
452
|
+
When `when` is `false`:
|
|
453
|
+
|
|
454
|
+
- Initialization does not run
|
|
455
|
+
- If `when` changes from `false` to `true`, initialization will run
|
|
456
|
+
- If initialization was already successful, it will not run again even if `when` becomes `true` again
|
|
457
|
+
|
|
458
|
+
### Retry Logic
|
|
459
|
+
|
|
460
|
+
When `retry` is set to `n`, the hook will attempt initialization up to `n + 1` times (initial attempt + `n` retries). Between attempts, it waits for `retryDelay` milliseconds.
|
|
461
|
+
|
|
462
|
+
### Timeout
|
|
463
|
+
|
|
464
|
+
When `timeout` is set:
|
|
465
|
+
|
|
466
|
+
- For async callbacks, a race condition is created between the callback and timeout
|
|
467
|
+
- If timeout expires first, an `InitTimeoutError` is thrown
|
|
468
|
+
- For sync callbacks, timeout is cleared immediately after execution
|
|
469
|
+
|
|
470
|
+
### Cleanup Functions
|
|
471
|
+
|
|
472
|
+
If the callback returns a cleanup function:
|
|
473
|
+
|
|
474
|
+
- It is called when the component unmounts
|
|
475
|
+
- It is called before re-initialization (when `reinitialize()` is called)
|
|
476
|
+
- It can be synchronous or asynchronous
|
|
477
|
+
- Only one cleanup function is stored at a time
|
|
478
|
+
|
|
479
|
+
### Error Handling
|
|
480
|
+
|
|
481
|
+
- Errors during initialization are caught and stored in the `error` property
|
|
482
|
+
- If retry is enabled, errors trigger retry attempts
|
|
483
|
+
- After all retries fail, the final error is stored
|
|
484
|
+
- Errors do not prevent component rendering
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Testing
|
|
489
|
+
|
|
490
|
+
This package maintains comprehensive test coverage to ensure reliability and stability.
|
|
491
|
+
|
|
492
|
+
### Test Coverage
|
|
493
|
+
|
|
494
|
+
📊 <a href="https://geon0529.github.io/usefy/coverage/use-init/src/index.html" target="_blank" rel="noopener noreferrer"><strong>View Detailed Coverage Report</strong></a> (GitHub Pages)
|
|
495
|
+
|
|
496
|
+
### Test Categories
|
|
497
|
+
|
|
498
|
+
<details>
|
|
499
|
+
<summary><strong>Basic Initialization Tests</strong></summary>
|
|
500
|
+
|
|
501
|
+
- Run callback once on mount
|
|
502
|
+
- Not run callback again on re-render
|
|
503
|
+
- Support synchronous callbacks
|
|
504
|
+
- Support asynchronous callbacks
|
|
505
|
+
- Track initialization state correctly
|
|
506
|
+
|
|
507
|
+
</details>
|
|
508
|
+
|
|
509
|
+
<details>
|
|
510
|
+
<summary><strong>Cleanup Function Tests</strong></summary>
|
|
511
|
+
|
|
512
|
+
- Call cleanup function on unmount
|
|
513
|
+
- Call cleanup function before re-initialization
|
|
514
|
+
- Support synchronous cleanup functions
|
|
515
|
+
- Support asynchronous cleanup functions
|
|
516
|
+
- Handle cleanup function errors gracefully
|
|
517
|
+
- Not call cleanup if callback doesn't return one
|
|
518
|
+
|
|
519
|
+
</details>
|
|
520
|
+
|
|
521
|
+
<details>
|
|
522
|
+
<summary><strong>Conditional Execution Tests</strong></summary>
|
|
523
|
+
|
|
524
|
+
- Not run when `when` is false
|
|
525
|
+
- Run when `when` changes from false to true
|
|
526
|
+
- Not run again if already initialized
|
|
527
|
+
- Respect `when` condition in `reinitialize()`
|
|
528
|
+
|
|
529
|
+
</details>
|
|
530
|
+
|
|
531
|
+
<details>
|
|
532
|
+
<summary><strong>Retry Logic Tests</strong></summary>
|
|
533
|
+
|
|
534
|
+
- Retry on failure with correct number of attempts
|
|
535
|
+
- Wait correct delay between retries
|
|
536
|
+
- Stop retrying after successful attempt
|
|
537
|
+
- Store final error after all retries fail
|
|
538
|
+
- Not retry if component unmounts during retry
|
|
539
|
+
|
|
540
|
+
</details>
|
|
541
|
+
|
|
542
|
+
<details>
|
|
543
|
+
<summary><strong>Timeout Tests</strong></summary>
|
|
544
|
+
|
|
545
|
+
- Timeout async callbacks that exceed timeout
|
|
546
|
+
- Not timeout sync callbacks
|
|
547
|
+
- Clear timeout after successful execution
|
|
548
|
+
- Throw InitTimeoutError on timeout
|
|
549
|
+
- Handle timeout with retry logic
|
|
550
|
+
|
|
551
|
+
</details>
|
|
552
|
+
|
|
553
|
+
<details>
|
|
554
|
+
<summary><strong>Manual Re-initialization Tests</strong></summary>
|
|
555
|
+
|
|
556
|
+
- Reinitialize when `reinitialize()` is called
|
|
557
|
+
- Respect `when` condition in `reinitialize()`
|
|
558
|
+
- Clean up previous initialization before re-running
|
|
559
|
+
- Update state correctly after re-initialization
|
|
560
|
+
|
|
561
|
+
</details>
|
|
562
|
+
|
|
563
|
+
<details>
|
|
564
|
+
<summary><strong>State Management Tests</strong></summary>
|
|
565
|
+
|
|
566
|
+
- Track `isInitializing` state correctly
|
|
567
|
+
- Track `isInitialized` state correctly
|
|
568
|
+
- Track `error` state correctly
|
|
569
|
+
- Update state only when component is mounted
|
|
570
|
+
- Handle rapid state changes correctly
|
|
571
|
+
|
|
572
|
+
</details>
|
|
573
|
+
|
|
574
|
+
<details>
|
|
575
|
+
<summary><strong>Edge Cases Tests</strong></summary>
|
|
576
|
+
|
|
577
|
+
- Handle component unmount during initialization
|
|
578
|
+
- Handle component unmount during retry
|
|
579
|
+
- Prevent concurrent initializations
|
|
580
|
+
- Handle callback reference changes
|
|
581
|
+
- Handle undefined/null errors gracefully
|
|
582
|
+
|
|
583
|
+
</details>
|
|
584
|
+
|
|
585
|
+
### Running Tests
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
# Run all tests
|
|
589
|
+
pnpm test
|
|
590
|
+
|
|
591
|
+
# Run tests in watch mode
|
|
592
|
+
pnpm test:watch
|
|
593
|
+
|
|
594
|
+
# Run tests with coverage report
|
|
595
|
+
pnpm test --coverage
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
## License
|
|
601
|
+
|
|
602
|
+
MIT © [mirunamu](https://github.com/geon0529)
|
|
603
|
+
|
|
604
|
+
This package is part of the [usefy](https://github.com/geon0529/usefy) monorepo.
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
<p align="center">
|
|
609
|
+
<sub>Built with care by the usefy team</sub>
|
|
610
|
+
</p>
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for useInit hook
|
|
3
|
+
*/
|
|
4
|
+
interface UseInitOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Only run initialization when this condition is true
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
when?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Number of retry attempts on failure
|
|
12
|
+
* @default 0
|
|
13
|
+
*/
|
|
14
|
+
retry?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Delay between retry attempts in milliseconds
|
|
17
|
+
* @default 1000
|
|
18
|
+
*/
|
|
19
|
+
retryDelay?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Timeout for initialization in milliseconds
|
|
22
|
+
* @default undefined (no timeout)
|
|
23
|
+
*/
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Result object returned by useInit hook
|
|
28
|
+
*/
|
|
29
|
+
interface UseInitResult {
|
|
30
|
+
/**
|
|
31
|
+
* Whether initialization has completed successfully
|
|
32
|
+
*/
|
|
33
|
+
isInitialized: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether initialization is currently in progress
|
|
36
|
+
*/
|
|
37
|
+
isInitializing: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Error that occurred during initialization, if any
|
|
40
|
+
*/
|
|
41
|
+
error: Error | null;
|
|
42
|
+
/**
|
|
43
|
+
* Manually trigger re-initialization (respects `when` condition)
|
|
44
|
+
*/
|
|
45
|
+
reinitialize: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Type for cleanup function returned by init callback
|
|
49
|
+
*/
|
|
50
|
+
type CleanupFn = () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Type for init callback function
|
|
53
|
+
*/
|
|
54
|
+
type InitCallback = () => void | CleanupFn | Promise<void | CleanupFn>;
|
|
55
|
+
/**
|
|
56
|
+
* A React hook for one-time initialization with async support, retry, timeout, and conditional execution.
|
|
57
|
+
*
|
|
58
|
+
* @param callback - The initialization function to run. Can be sync or async.
|
|
59
|
+
* Can optionally return a cleanup function.
|
|
60
|
+
* @param options - Configuration options for initialization
|
|
61
|
+
* @returns Object containing initialization state and control functions
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Basic synchronous initialization
|
|
65
|
+
* useInit(() => {
|
|
66
|
+
* console.log('Component initialized');
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // With cleanup function
|
|
71
|
+
* useInit(() => {
|
|
72
|
+
* const subscription = eventBus.subscribe();
|
|
73
|
+
* return () => subscription.unsubscribe();
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Async initialization with status tracking
|
|
78
|
+
* const { isInitialized, isInitializing, error } = useInit(async () => {
|
|
79
|
+
* await loadConfiguration();
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Conditional initialization
|
|
84
|
+
* useInit(() => {
|
|
85
|
+
* initializeAnalytics();
|
|
86
|
+
* }, { when: isProduction });
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // With retry and timeout
|
|
90
|
+
* const { error, reinitialize } = useInit(async () => {
|
|
91
|
+
* await connectToServer();
|
|
92
|
+
* }, {
|
|
93
|
+
* retry: 3,
|
|
94
|
+
* retryDelay: 1000,
|
|
95
|
+
* timeout: 5000
|
|
96
|
+
* });
|
|
97
|
+
*/
|
|
98
|
+
declare function useInit(callback: InitCallback, options?: UseInitOptions): UseInitResult;
|
|
99
|
+
|
|
100
|
+
export { type UseInitOptions, type UseInitResult, useInit };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for useInit hook
|
|
3
|
+
*/
|
|
4
|
+
interface UseInitOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Only run initialization when this condition is true
|
|
7
|
+
* @default true
|
|
8
|
+
*/
|
|
9
|
+
when?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Number of retry attempts on failure
|
|
12
|
+
* @default 0
|
|
13
|
+
*/
|
|
14
|
+
retry?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Delay between retry attempts in milliseconds
|
|
17
|
+
* @default 1000
|
|
18
|
+
*/
|
|
19
|
+
retryDelay?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Timeout for initialization in milliseconds
|
|
22
|
+
* @default undefined (no timeout)
|
|
23
|
+
*/
|
|
24
|
+
timeout?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Result object returned by useInit hook
|
|
28
|
+
*/
|
|
29
|
+
interface UseInitResult {
|
|
30
|
+
/**
|
|
31
|
+
* Whether initialization has completed successfully
|
|
32
|
+
*/
|
|
33
|
+
isInitialized: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Whether initialization is currently in progress
|
|
36
|
+
*/
|
|
37
|
+
isInitializing: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Error that occurred during initialization, if any
|
|
40
|
+
*/
|
|
41
|
+
error: Error | null;
|
|
42
|
+
/**
|
|
43
|
+
* Manually trigger re-initialization (respects `when` condition)
|
|
44
|
+
*/
|
|
45
|
+
reinitialize: () => void;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Type for cleanup function returned by init callback
|
|
49
|
+
*/
|
|
50
|
+
type CleanupFn = () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Type for init callback function
|
|
53
|
+
*/
|
|
54
|
+
type InitCallback = () => void | CleanupFn | Promise<void | CleanupFn>;
|
|
55
|
+
/**
|
|
56
|
+
* A React hook for one-time initialization with async support, retry, timeout, and conditional execution.
|
|
57
|
+
*
|
|
58
|
+
* @param callback - The initialization function to run. Can be sync or async.
|
|
59
|
+
* Can optionally return a cleanup function.
|
|
60
|
+
* @param options - Configuration options for initialization
|
|
61
|
+
* @returns Object containing initialization state and control functions
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* // Basic synchronous initialization
|
|
65
|
+
* useInit(() => {
|
|
66
|
+
* console.log('Component initialized');
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // With cleanup function
|
|
71
|
+
* useInit(() => {
|
|
72
|
+
* const subscription = eventBus.subscribe();
|
|
73
|
+
* return () => subscription.unsubscribe();
|
|
74
|
+
* });
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* // Async initialization with status tracking
|
|
78
|
+
* const { isInitialized, isInitializing, error } = useInit(async () => {
|
|
79
|
+
* await loadConfiguration();
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Conditional initialization
|
|
84
|
+
* useInit(() => {
|
|
85
|
+
* initializeAnalytics();
|
|
86
|
+
* }, { when: isProduction });
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* // With retry and timeout
|
|
90
|
+
* const { error, reinitialize } = useInit(async () => {
|
|
91
|
+
* await connectToServer();
|
|
92
|
+
* }, {
|
|
93
|
+
* retry: 3,
|
|
94
|
+
* retryDelay: 1000,
|
|
95
|
+
* timeout: 5000
|
|
96
|
+
* });
|
|
97
|
+
*/
|
|
98
|
+
declare function useInit(callback: InitCallback, options?: UseInitOptions): UseInitResult;
|
|
99
|
+
|
|
100
|
+
export { type UseInitOptions, type UseInitResult, useInit };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
useInit: () => useInit
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/useInit.ts
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
var InitTimeoutError = class extends Error {
|
|
30
|
+
constructor(timeout) {
|
|
31
|
+
super(`Initialization timed out after ${timeout}ms`);
|
|
32
|
+
this.name = "InitTimeoutError";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
function useInit(callback, options = {}) {
|
|
36
|
+
const { when = true, retry = 0, retryDelay = 1e3, timeout } = options;
|
|
37
|
+
const [state, setState] = (0, import_react.useState)({
|
|
38
|
+
isInitialized: false,
|
|
39
|
+
isInitializing: false,
|
|
40
|
+
error: null
|
|
41
|
+
});
|
|
42
|
+
const callbackRef = (0, import_react.useRef)(callback);
|
|
43
|
+
const cleanupRef = (0, import_react.useRef)(null);
|
|
44
|
+
const hasInitializedRef = (0, import_react.useRef)(false);
|
|
45
|
+
const mountedRef = (0, import_react.useRef)(true);
|
|
46
|
+
const initializingRef = (0, import_react.useRef)(false);
|
|
47
|
+
callbackRef.current = callback;
|
|
48
|
+
const runInit = (0, import_react.useCallback)(async () => {
|
|
49
|
+
if (initializingRef.current) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
initializingRef.current = true;
|
|
53
|
+
if (cleanupRef.current) {
|
|
54
|
+
cleanupRef.current();
|
|
55
|
+
cleanupRef.current = null;
|
|
56
|
+
}
|
|
57
|
+
setState({
|
|
58
|
+
isInitialized: false,
|
|
59
|
+
isInitializing: true,
|
|
60
|
+
error: null
|
|
61
|
+
});
|
|
62
|
+
let lastError = null;
|
|
63
|
+
const maxAttempts = retry + 1;
|
|
64
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
65
|
+
if (!mountedRef.current) {
|
|
66
|
+
initializingRef.current = false;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
let result;
|
|
71
|
+
if (timeout !== void 0) {
|
|
72
|
+
let timeoutId;
|
|
73
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
74
|
+
timeoutId = setTimeout(() => {
|
|
75
|
+
reject(new InitTimeoutError(timeout));
|
|
76
|
+
}, timeout);
|
|
77
|
+
});
|
|
78
|
+
const callbackResult = callbackRef.current();
|
|
79
|
+
if (callbackResult instanceof Promise) {
|
|
80
|
+
try {
|
|
81
|
+
result = await Promise.race([callbackResult, timeoutPromise]);
|
|
82
|
+
} finally {
|
|
83
|
+
if (timeoutId !== void 0) {
|
|
84
|
+
clearTimeout(timeoutId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
if (timeoutId !== void 0) {
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
}
|
|
91
|
+
result = callbackResult;
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
const callbackResult = callbackRef.current();
|
|
95
|
+
if (callbackResult instanceof Promise) {
|
|
96
|
+
result = await callbackResult;
|
|
97
|
+
} else {
|
|
98
|
+
result = callbackResult;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (typeof result === "function") {
|
|
102
|
+
cleanupRef.current = result;
|
|
103
|
+
}
|
|
104
|
+
if (mountedRef.current) {
|
|
105
|
+
hasInitializedRef.current = true;
|
|
106
|
+
setState({
|
|
107
|
+
isInitialized: true,
|
|
108
|
+
isInitializing: false,
|
|
109
|
+
error: null
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
initializingRef.current = false;
|
|
113
|
+
return;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
116
|
+
if (attempt < maxAttempts - 1 && mountedRef.current) {
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (mountedRef.current) {
|
|
122
|
+
setState({
|
|
123
|
+
isInitialized: false,
|
|
124
|
+
isInitializing: false,
|
|
125
|
+
error: lastError
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
initializingRef.current = false;
|
|
129
|
+
}, [retry, retryDelay, timeout]);
|
|
130
|
+
const reinitialize = (0, import_react.useCallback)(() => {
|
|
131
|
+
if (!when) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
runInit();
|
|
135
|
+
}, [when, runInit]);
|
|
136
|
+
const prevWhenRef = (0, import_react.useRef)(when);
|
|
137
|
+
const hasRunOnceRef = (0, import_react.useRef)(false);
|
|
138
|
+
(0, import_react.useEffect)(() => {
|
|
139
|
+
mountedRef.current = true;
|
|
140
|
+
const whenJustBecameTrue = !prevWhenRef.current && when;
|
|
141
|
+
const shouldInit = when && !hasInitializedRef.current && (!hasRunOnceRef.current || whenJustBecameTrue);
|
|
142
|
+
prevWhenRef.current = when;
|
|
143
|
+
if (shouldInit) {
|
|
144
|
+
hasRunOnceRef.current = true;
|
|
145
|
+
runInit();
|
|
146
|
+
}
|
|
147
|
+
return () => {
|
|
148
|
+
mountedRef.current = false;
|
|
149
|
+
if (cleanupRef.current) {
|
|
150
|
+
cleanupRef.current();
|
|
151
|
+
cleanupRef.current = null;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}, [when, runInit]);
|
|
155
|
+
return {
|
|
156
|
+
isInitialized: state.isInitialized,
|
|
157
|
+
isInitializing: state.isInitializing,
|
|
158
|
+
error: state.error,
|
|
159
|
+
reinitialize
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
163
|
+
0 && (module.exports = {
|
|
164
|
+
useInit
|
|
165
|
+
});
|
|
166
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/useInit.ts"],"sourcesContent":["export { useInit, type UseInitOptions, type UseInitResult } from \"./useInit\";\n","import { useState, useEffect, useRef, useCallback } from \"react\";\n\n/**\n * Options for useInit hook\n */\nexport interface UseInitOptions {\n /**\n * Only run initialization when this condition is true\n * @default true\n */\n when?: boolean;\n /**\n * Number of retry attempts on failure\n * @default 0\n */\n retry?: number;\n /**\n * Delay between retry attempts in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Timeout for initialization in milliseconds\n * @default undefined (no timeout)\n */\n timeout?: number;\n}\n\n/**\n * Result object returned by useInit hook\n */\nexport interface UseInitResult {\n /**\n * Whether initialization has completed successfully\n */\n isInitialized: boolean;\n /**\n * Whether initialization is currently in progress\n */\n isInitializing: boolean;\n /**\n * Error that occurred during initialization, if any\n */\n error: Error | null;\n /**\n * Manually trigger re-initialization (respects `when` condition)\n */\n reinitialize: () => void;\n}\n\n/**\n * Type for cleanup function returned by init callback\n */\ntype CleanupFn = () => void;\n\n/**\n * Type for init callback function\n */\ntype InitCallback = () => void | CleanupFn | Promise<void | CleanupFn>;\n\n/**\n * Custom error for timeout\n */\nclass InitTimeoutError extends Error {\n constructor(timeout: number) {\n super(`Initialization timed out after ${timeout}ms`);\n this.name = \"InitTimeoutError\";\n }\n}\n\n/**\n * A React hook for one-time initialization with async support, retry, timeout, and conditional execution.\n *\n * @param callback - The initialization function to run. Can be sync or async.\n * Can optionally return a cleanup function.\n * @param options - Configuration options for initialization\n * @returns Object containing initialization state and control functions\n *\n * @example\n * // Basic synchronous initialization\n * useInit(() => {\n * console.log('Component initialized');\n * });\n *\n * @example\n * // With cleanup function\n * useInit(() => {\n * const subscription = eventBus.subscribe();\n * return () => subscription.unsubscribe();\n * });\n *\n * @example\n * // Async initialization with status tracking\n * const { isInitialized, isInitializing, error } = useInit(async () => {\n * await loadConfiguration();\n * });\n *\n * @example\n * // Conditional initialization\n * useInit(() => {\n * initializeAnalytics();\n * }, { when: isProduction });\n *\n * @example\n * // With retry and timeout\n * const { error, reinitialize } = useInit(async () => {\n * await connectToServer();\n * }, {\n * retry: 3,\n * retryDelay: 1000,\n * timeout: 5000\n * });\n */\nexport function useInit(\n callback: InitCallback,\n options: UseInitOptions = {}\n): UseInitResult {\n const { when = true, retry = 0, retryDelay = 1000, timeout } = options;\n\n const [state, setState] = useState<{\n isInitialized: boolean;\n isInitializing: boolean;\n error: Error | null;\n }>({\n isInitialized: false,\n isInitializing: false,\n error: null,\n });\n\n const callbackRef = useRef<InitCallback>(callback);\n const cleanupRef = useRef<CleanupFn | null>(null);\n const hasInitializedRef = useRef(false);\n const mountedRef = useRef(true);\n const initializingRef = useRef(false);\n\n // Always update callback ref to latest version\n callbackRef.current = callback;\n\n const runInit = useCallback(async () => {\n // Prevent concurrent initializations\n if (initializingRef.current) {\n return;\n }\n\n initializingRef.current = true;\n\n // Clean up previous initialization if any\n if (cleanupRef.current) {\n cleanupRef.current();\n cleanupRef.current = null;\n }\n\n setState({\n isInitialized: false,\n isInitializing: true,\n error: null,\n });\n\n let lastError: Error | null = null;\n const maxAttempts = retry + 1;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (!mountedRef.current) {\n initializingRef.current = false;\n return;\n }\n\n try {\n let result: void | CleanupFn;\n\n if (timeout !== undefined) {\n // Race between callback and timeout\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new InitTimeoutError(timeout));\n }, timeout);\n });\n\n const callbackResult = callbackRef.current();\n\n if (callbackResult instanceof Promise) {\n try {\n result = await Promise.race([callbackResult, timeoutPromise]);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n } else {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n result = callbackResult;\n }\n } else {\n const callbackResult = callbackRef.current();\n if (callbackResult instanceof Promise) {\n result = await callbackResult;\n } else {\n result = callbackResult;\n }\n }\n\n // Store cleanup function if returned\n if (typeof result === \"function\") {\n cleanupRef.current = result;\n }\n\n if (mountedRef.current) {\n hasInitializedRef.current = true;\n setState({\n isInitialized: true,\n isInitializing: false,\n error: null,\n });\n }\n\n initializingRef.current = false;\n return;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n // If not the last attempt and still mounted, wait before retrying\n if (attempt < maxAttempts - 1 && mountedRef.current) {\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n }\n }\n }\n\n // All attempts failed\n if (mountedRef.current) {\n setState({\n isInitialized: false,\n isInitializing: false,\n error: lastError,\n });\n }\n\n initializingRef.current = false;\n }, [retry, retryDelay, timeout]);\n\n const reinitialize = useCallback(() => {\n if (!when) {\n return;\n }\n runInit();\n }, [when, runInit]);\n\n // Track when condition changes from false to true\n const prevWhenRef = useRef(when);\n const hasRunOnceRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n\n // Run initialization if:\n // 1. `when` is true AND\n // 2. Never successfully initialized AND\n // 3. Either first run OR `when` just changed from false to true\n const whenJustBecameTrue = !prevWhenRef.current && when;\n const shouldInit =\n when &&\n !hasInitializedRef.current &&\n (!hasRunOnceRef.current || whenJustBecameTrue);\n\n prevWhenRef.current = when;\n\n if (shouldInit) {\n hasRunOnceRef.current = true;\n runInit();\n }\n\n return () => {\n mountedRef.current = false;\n if (cleanupRef.current) {\n cleanupRef.current();\n cleanupRef.current = null;\n }\n };\n }, [when, runInit]);\n\n return {\n isInitialized: state.isInitialized,\n isInitializing: state.isInitializing,\n error: state.error,\n reinitialize,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// src/useInit.ts
|
|
2
|
+
import { useState, useEffect, useRef, useCallback } from "react";
|
|
3
|
+
var InitTimeoutError = class extends Error {
|
|
4
|
+
constructor(timeout) {
|
|
5
|
+
super(`Initialization timed out after ${timeout}ms`);
|
|
6
|
+
this.name = "InitTimeoutError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
function useInit(callback, options = {}) {
|
|
10
|
+
const { when = true, retry = 0, retryDelay = 1e3, timeout } = options;
|
|
11
|
+
const [state, setState] = useState({
|
|
12
|
+
isInitialized: false,
|
|
13
|
+
isInitializing: false,
|
|
14
|
+
error: null
|
|
15
|
+
});
|
|
16
|
+
const callbackRef = useRef(callback);
|
|
17
|
+
const cleanupRef = useRef(null);
|
|
18
|
+
const hasInitializedRef = useRef(false);
|
|
19
|
+
const mountedRef = useRef(true);
|
|
20
|
+
const initializingRef = useRef(false);
|
|
21
|
+
callbackRef.current = callback;
|
|
22
|
+
const runInit = useCallback(async () => {
|
|
23
|
+
if (initializingRef.current) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
initializingRef.current = true;
|
|
27
|
+
if (cleanupRef.current) {
|
|
28
|
+
cleanupRef.current();
|
|
29
|
+
cleanupRef.current = null;
|
|
30
|
+
}
|
|
31
|
+
setState({
|
|
32
|
+
isInitialized: false,
|
|
33
|
+
isInitializing: true,
|
|
34
|
+
error: null
|
|
35
|
+
});
|
|
36
|
+
let lastError = null;
|
|
37
|
+
const maxAttempts = retry + 1;
|
|
38
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
39
|
+
if (!mountedRef.current) {
|
|
40
|
+
initializingRef.current = false;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
let result;
|
|
45
|
+
if (timeout !== void 0) {
|
|
46
|
+
let timeoutId;
|
|
47
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
48
|
+
timeoutId = setTimeout(() => {
|
|
49
|
+
reject(new InitTimeoutError(timeout));
|
|
50
|
+
}, timeout);
|
|
51
|
+
});
|
|
52
|
+
const callbackResult = callbackRef.current();
|
|
53
|
+
if (callbackResult instanceof Promise) {
|
|
54
|
+
try {
|
|
55
|
+
result = await Promise.race([callbackResult, timeoutPromise]);
|
|
56
|
+
} finally {
|
|
57
|
+
if (timeoutId !== void 0) {
|
|
58
|
+
clearTimeout(timeoutId);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
if (timeoutId !== void 0) {
|
|
63
|
+
clearTimeout(timeoutId);
|
|
64
|
+
}
|
|
65
|
+
result = callbackResult;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
const callbackResult = callbackRef.current();
|
|
69
|
+
if (callbackResult instanceof Promise) {
|
|
70
|
+
result = await callbackResult;
|
|
71
|
+
} else {
|
|
72
|
+
result = callbackResult;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (typeof result === "function") {
|
|
76
|
+
cleanupRef.current = result;
|
|
77
|
+
}
|
|
78
|
+
if (mountedRef.current) {
|
|
79
|
+
hasInitializedRef.current = true;
|
|
80
|
+
setState({
|
|
81
|
+
isInitialized: true,
|
|
82
|
+
isInitializing: false,
|
|
83
|
+
error: null
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
initializingRef.current = false;
|
|
87
|
+
return;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
90
|
+
if (attempt < maxAttempts - 1 && mountedRef.current) {
|
|
91
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (mountedRef.current) {
|
|
96
|
+
setState({
|
|
97
|
+
isInitialized: false,
|
|
98
|
+
isInitializing: false,
|
|
99
|
+
error: lastError
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
initializingRef.current = false;
|
|
103
|
+
}, [retry, retryDelay, timeout]);
|
|
104
|
+
const reinitialize = useCallback(() => {
|
|
105
|
+
if (!when) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
runInit();
|
|
109
|
+
}, [when, runInit]);
|
|
110
|
+
const prevWhenRef = useRef(when);
|
|
111
|
+
const hasRunOnceRef = useRef(false);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
mountedRef.current = true;
|
|
114
|
+
const whenJustBecameTrue = !prevWhenRef.current && when;
|
|
115
|
+
const shouldInit = when && !hasInitializedRef.current && (!hasRunOnceRef.current || whenJustBecameTrue);
|
|
116
|
+
prevWhenRef.current = when;
|
|
117
|
+
if (shouldInit) {
|
|
118
|
+
hasRunOnceRef.current = true;
|
|
119
|
+
runInit();
|
|
120
|
+
}
|
|
121
|
+
return () => {
|
|
122
|
+
mountedRef.current = false;
|
|
123
|
+
if (cleanupRef.current) {
|
|
124
|
+
cleanupRef.current();
|
|
125
|
+
cleanupRef.current = null;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}, [when, runInit]);
|
|
129
|
+
return {
|
|
130
|
+
isInitialized: state.isInitialized,
|
|
131
|
+
isInitializing: state.isInitializing,
|
|
132
|
+
error: state.error,
|
|
133
|
+
reinitialize
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
useInit
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useInit.ts"],"sourcesContent":["import { useState, useEffect, useRef, useCallback } from \"react\";\n\n/**\n * Options for useInit hook\n */\nexport interface UseInitOptions {\n /**\n * Only run initialization when this condition is true\n * @default true\n */\n when?: boolean;\n /**\n * Number of retry attempts on failure\n * @default 0\n */\n retry?: number;\n /**\n * Delay between retry attempts in milliseconds\n * @default 1000\n */\n retryDelay?: number;\n /**\n * Timeout for initialization in milliseconds\n * @default undefined (no timeout)\n */\n timeout?: number;\n}\n\n/**\n * Result object returned by useInit hook\n */\nexport interface UseInitResult {\n /**\n * Whether initialization has completed successfully\n */\n isInitialized: boolean;\n /**\n * Whether initialization is currently in progress\n */\n isInitializing: boolean;\n /**\n * Error that occurred during initialization, if any\n */\n error: Error | null;\n /**\n * Manually trigger re-initialization (respects `when` condition)\n */\n reinitialize: () => void;\n}\n\n/**\n * Type for cleanup function returned by init callback\n */\ntype CleanupFn = () => void;\n\n/**\n * Type for init callback function\n */\ntype InitCallback = () => void | CleanupFn | Promise<void | CleanupFn>;\n\n/**\n * Custom error for timeout\n */\nclass InitTimeoutError extends Error {\n constructor(timeout: number) {\n super(`Initialization timed out after ${timeout}ms`);\n this.name = \"InitTimeoutError\";\n }\n}\n\n/**\n * A React hook for one-time initialization with async support, retry, timeout, and conditional execution.\n *\n * @param callback - The initialization function to run. Can be sync or async.\n * Can optionally return a cleanup function.\n * @param options - Configuration options for initialization\n * @returns Object containing initialization state and control functions\n *\n * @example\n * // Basic synchronous initialization\n * useInit(() => {\n * console.log('Component initialized');\n * });\n *\n * @example\n * // With cleanup function\n * useInit(() => {\n * const subscription = eventBus.subscribe();\n * return () => subscription.unsubscribe();\n * });\n *\n * @example\n * // Async initialization with status tracking\n * const { isInitialized, isInitializing, error } = useInit(async () => {\n * await loadConfiguration();\n * });\n *\n * @example\n * // Conditional initialization\n * useInit(() => {\n * initializeAnalytics();\n * }, { when: isProduction });\n *\n * @example\n * // With retry and timeout\n * const { error, reinitialize } = useInit(async () => {\n * await connectToServer();\n * }, {\n * retry: 3,\n * retryDelay: 1000,\n * timeout: 5000\n * });\n */\nexport function useInit(\n callback: InitCallback,\n options: UseInitOptions = {}\n): UseInitResult {\n const { when = true, retry = 0, retryDelay = 1000, timeout } = options;\n\n const [state, setState] = useState<{\n isInitialized: boolean;\n isInitializing: boolean;\n error: Error | null;\n }>({\n isInitialized: false,\n isInitializing: false,\n error: null,\n });\n\n const callbackRef = useRef<InitCallback>(callback);\n const cleanupRef = useRef<CleanupFn | null>(null);\n const hasInitializedRef = useRef(false);\n const mountedRef = useRef(true);\n const initializingRef = useRef(false);\n\n // Always update callback ref to latest version\n callbackRef.current = callback;\n\n const runInit = useCallback(async () => {\n // Prevent concurrent initializations\n if (initializingRef.current) {\n return;\n }\n\n initializingRef.current = true;\n\n // Clean up previous initialization if any\n if (cleanupRef.current) {\n cleanupRef.current();\n cleanupRef.current = null;\n }\n\n setState({\n isInitialized: false,\n isInitializing: true,\n error: null,\n });\n\n let lastError: Error | null = null;\n const maxAttempts = retry + 1;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (!mountedRef.current) {\n initializingRef.current = false;\n return;\n }\n\n try {\n let result: void | CleanupFn;\n\n if (timeout !== undefined) {\n // Race between callback and timeout\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new InitTimeoutError(timeout));\n }, timeout);\n });\n\n const callbackResult = callbackRef.current();\n\n if (callbackResult instanceof Promise) {\n try {\n result = await Promise.race([callbackResult, timeoutPromise]);\n } finally {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n }\n } else {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n result = callbackResult;\n }\n } else {\n const callbackResult = callbackRef.current();\n if (callbackResult instanceof Promise) {\n result = await callbackResult;\n } else {\n result = callbackResult;\n }\n }\n\n // Store cleanup function if returned\n if (typeof result === \"function\") {\n cleanupRef.current = result;\n }\n\n if (mountedRef.current) {\n hasInitializedRef.current = true;\n setState({\n isInitialized: true,\n isInitializing: false,\n error: null,\n });\n }\n\n initializingRef.current = false;\n return;\n } catch (err) {\n lastError = err instanceof Error ? err : new Error(String(err));\n\n // If not the last attempt and still mounted, wait before retrying\n if (attempt < maxAttempts - 1 && mountedRef.current) {\n await new Promise((resolve) => setTimeout(resolve, retryDelay));\n }\n }\n }\n\n // All attempts failed\n if (mountedRef.current) {\n setState({\n isInitialized: false,\n isInitializing: false,\n error: lastError,\n });\n }\n\n initializingRef.current = false;\n }, [retry, retryDelay, timeout]);\n\n const reinitialize = useCallback(() => {\n if (!when) {\n return;\n }\n runInit();\n }, [when, runInit]);\n\n // Track when condition changes from false to true\n const prevWhenRef = useRef(when);\n const hasRunOnceRef = useRef(false);\n\n useEffect(() => {\n mountedRef.current = true;\n\n // Run initialization if:\n // 1. `when` is true AND\n // 2. Never successfully initialized AND\n // 3. Either first run OR `when` just changed from false to true\n const whenJustBecameTrue = !prevWhenRef.current && when;\n const shouldInit =\n when &&\n !hasInitializedRef.current &&\n (!hasRunOnceRef.current || whenJustBecameTrue);\n\n prevWhenRef.current = when;\n\n if (shouldInit) {\n hasRunOnceRef.current = true;\n runInit();\n }\n\n return () => {\n mountedRef.current = false;\n if (cleanupRef.current) {\n cleanupRef.current();\n cleanupRef.current = null;\n }\n };\n }, [when, runInit]);\n\n return {\n isInitialized: state.isInitialized,\n isInitializing: state.isInitializing,\n error: state.error,\n reinitialize,\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@usefy/use-init",
|
|
3
|
+
"version": "0.0.24",
|
|
4
|
+
"description": "A React hook for one-time initialization with async support, retry, timeout, and conditional execution",
|
|
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-init"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"keywords": [
|
|
44
|
+
"react",
|
|
45
|
+
"hooks",
|
|
46
|
+
"init",
|
|
47
|
+
"initialization",
|
|
48
|
+
"mount",
|
|
49
|
+
"async",
|
|
50
|
+
"retry",
|
|
51
|
+
"timeout"
|
|
52
|
+
],
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"clean": "rimraf dist"
|
|
60
|
+
}
|
|
61
|
+
}
|