@delightui/components 0.1.109 → 0.1.111
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/cjs/components/molecules/Search/Search.d.ts +18 -0
- package/dist/cjs/components/molecules/Search/Search.presenter.d.ts +320 -0
- package/dist/cjs/components/molecules/Search/Search.types.d.ts +53 -0
- package/dist/cjs/components/molecules/Search/index.d.ts +2 -0
- package/dist/cjs/components/molecules/index.d.ts +2 -0
- package/dist/cjs/components/utils/RenderStateView/RenderStateView.types.d.ts +4 -0
- package/dist/cjs/components/utils/RenderStateView/usePresenter.d.ts +1 -0
- package/dist/cjs/components/utils/index.d.ts +2 -0
- package/dist/cjs/components/utils/useDebounce/index.d.ts +1 -0
- package/dist/cjs/components/utils/useDebounce/useDebounce.d.ts +10 -0
- package/dist/cjs/components/utils/useInflateView/index.d.ts +0 -1
- package/dist/cjs/components/utils/useInflateView/useInflateView.d.ts +1 -1
- package/dist/cjs/library.css +66 -0
- package/dist/cjs/library.js +3 -3
- package/dist/cjs/library.js.map +1 -1
- package/dist/esm/components/molecules/Search/Search.d.ts +18 -0
- package/dist/esm/components/molecules/Search/Search.presenter.d.ts +320 -0
- package/dist/esm/components/molecules/Search/Search.types.d.ts +53 -0
- package/dist/esm/components/molecules/Search/index.d.ts +2 -0
- package/dist/esm/components/molecules/index.d.ts +2 -0
- package/dist/esm/components/utils/RenderStateView/RenderStateView.types.d.ts +4 -0
- package/dist/esm/components/utils/RenderStateView/usePresenter.d.ts +1 -0
- package/dist/esm/components/utils/index.d.ts +2 -0
- package/dist/esm/components/utils/useDebounce/index.d.ts +1 -0
- package/dist/esm/components/utils/useDebounce/useDebounce.d.ts +10 -0
- package/dist/esm/components/utils/useInflateView/index.d.ts +0 -1
- package/dist/esm/components/utils/useInflateView/useInflateView.d.ts +1 -1
- package/dist/esm/library.css +66 -0
- package/dist/esm/library.js +3 -3
- package/dist/esm/library.js.map +1 -1
- package/dist/index.d.ts +83 -10
- package/docs/README.md +6 -0
- package/docs/components/atoms/Input.md +0 -63
- package/docs/components/molecules/Search.md +710 -0
- package/docs/components/utils/RenderStateView.md +137 -38
- package/docs/components/utils/useDebounce.md +576 -0
- package/package.json +1 -1
- package/dist/cjs/components/utils/useInflateView/useInflateView.types.d.ts +0 -12
- package/dist/esm/components/utils/useInflateView/useInflateView.types.d.ts +0 -12
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# useDebounce
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
A React hook that debounces callback functions, preventing them from being called too frequently. This is particularly useful for optimizing performance in scenarios like search inputs, API calls, window resize handlers, or any function that might be triggered rapidly. The hook provides both a debounced version of the callback and a cancel function to clear pending executions.
|
|
6
|
+
|
|
7
|
+
## Aliases
|
|
8
|
+
|
|
9
|
+
- useDebounce
|
|
10
|
+
- Debounce Hook
|
|
11
|
+
- Throttled Callback Hook
|
|
12
|
+
- Delayed Execution Hook
|
|
13
|
+
|
|
14
|
+
## Hook Signature
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
function useDebounce<T extends (...args: any[]) => void>(
|
|
18
|
+
callback: T,
|
|
19
|
+
delay: number
|
|
20
|
+
): {
|
|
21
|
+
debouncedCallback: (...args: Parameters<T>) => void;
|
|
22
|
+
cancel: () => void;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Parameters
|
|
27
|
+
|
|
28
|
+
| Parameter | Type | Required | Description |
|
|
29
|
+
|-----------|------|----------|-------------|
|
|
30
|
+
| `callback` | `T extends (...args: any[]) => void` | Yes | The function to debounce |
|
|
31
|
+
| `delay` | `number` | Yes | The debounce delay in milliseconds |
|
|
32
|
+
|
|
33
|
+
## Return Value
|
|
34
|
+
|
|
35
|
+
Returns an object with:
|
|
36
|
+
- **`debouncedCallback`**: The debounced version of the callback function
|
|
37
|
+
- **`cancel`**: Function to cancel any pending execution
|
|
38
|
+
|
|
39
|
+
## Examples
|
|
40
|
+
|
|
41
|
+
### Basic Search Input Debouncing
|
|
42
|
+
```tsx
|
|
43
|
+
import React, { useState } from 'react';
|
|
44
|
+
import { useDebounce, Input } from '@delightui/components';
|
|
45
|
+
|
|
46
|
+
function SearchExample() {
|
|
47
|
+
const [query, setQuery] = useState('');
|
|
48
|
+
const [results, setResults] = useState([]);
|
|
49
|
+
const [loading, setLoading] = useState(false);
|
|
50
|
+
|
|
51
|
+
const performSearch = async (searchQuery: string) => {
|
|
52
|
+
if (!searchQuery.trim()) return;
|
|
53
|
+
|
|
54
|
+
setLoading(true);
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
setResults(data.results);
|
|
59
|
+
} finally {
|
|
60
|
+
setLoading(false);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const { debouncedCallback: debouncedSearch } = useDebounce(performSearch, 300);
|
|
65
|
+
|
|
66
|
+
const handleInputChange = (value: string) => {
|
|
67
|
+
setQuery(value);
|
|
68
|
+
debouncedSearch(value);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
<Input
|
|
74
|
+
value={query}
|
|
75
|
+
onValueChange={handleInputChange}
|
|
76
|
+
placeholder="Search..."
|
|
77
|
+
/>
|
|
78
|
+
{loading && <div>Searching...</div>}
|
|
79
|
+
<div>
|
|
80
|
+
{results.map(result => (
|
|
81
|
+
<div key={result.id}>{result.title}</div>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Form Validation with Debouncing
|
|
90
|
+
```tsx
|
|
91
|
+
function FormValidationExample() {
|
|
92
|
+
const [formData, setFormData] = useState({
|
|
93
|
+
username: '',
|
|
94
|
+
email: ''
|
|
95
|
+
});
|
|
96
|
+
const [errors, setErrors] = useState({});
|
|
97
|
+
const [validating, setValidating] = useState(false);
|
|
98
|
+
|
|
99
|
+
const validateForm = async (data: typeof formData) => {
|
|
100
|
+
setValidating(true);
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch('/api/validate', {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify(data)
|
|
106
|
+
});
|
|
107
|
+
const validation = await response.json();
|
|
108
|
+
setErrors(validation.errors || {});
|
|
109
|
+
} finally {
|
|
110
|
+
setValidating(false);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const { debouncedCallback: debouncedValidate } = useDebounce(validateForm, 500);
|
|
115
|
+
|
|
116
|
+
const updateField = (field: string, value: string) => {
|
|
117
|
+
const newData = { ...formData, [field]: value };
|
|
118
|
+
setFormData(newData);
|
|
119
|
+
debouncedValidate(newData);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<Form>
|
|
124
|
+
<FormField
|
|
125
|
+
name="username"
|
|
126
|
+
label="Username"
|
|
127
|
+
invalid={!!errors.username}
|
|
128
|
+
message={errors.username}
|
|
129
|
+
>
|
|
130
|
+
<Input
|
|
131
|
+
value={formData.username}
|
|
132
|
+
onValueChange={(value) => updateField('username', value)}
|
|
133
|
+
placeholder="Enter username"
|
|
134
|
+
/>
|
|
135
|
+
</FormField>
|
|
136
|
+
|
|
137
|
+
<FormField
|
|
138
|
+
name="email"
|
|
139
|
+
label="Email"
|
|
140
|
+
invalid={!!errors.email}
|
|
141
|
+
message={errors.email}
|
|
142
|
+
>
|
|
143
|
+
<Input
|
|
144
|
+
value={formData.email}
|
|
145
|
+
onValueChange={(value) => updateField('email', value)}
|
|
146
|
+
placeholder="Enter email"
|
|
147
|
+
/>
|
|
148
|
+
</FormField>
|
|
149
|
+
|
|
150
|
+
{validating && (
|
|
151
|
+
<div className="validation-indicator">
|
|
152
|
+
<Spinner size="Small" />
|
|
153
|
+
<Text type="BodySmall">Validating...</Text>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</Form>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Auto-save Functionality
|
|
162
|
+
```tsx
|
|
163
|
+
function AutoSaveExample() {
|
|
164
|
+
const [document, setDocument] = useState({
|
|
165
|
+
title: '',
|
|
166
|
+
content: '',
|
|
167
|
+
lastSaved: null
|
|
168
|
+
});
|
|
169
|
+
const [saveStatus, setSaveStatus] = useState('saved'); // 'saving', 'saved', 'error'
|
|
170
|
+
|
|
171
|
+
const saveDocument = async (docData: typeof document) => {
|
|
172
|
+
setSaveStatus('saving');
|
|
173
|
+
try {
|
|
174
|
+
const response = await fetch('/api/documents/save', {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: { 'Content-Type': 'application/json' },
|
|
177
|
+
body: JSON.stringify(docData)
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (response.ok) {
|
|
181
|
+
setSaveStatus('saved');
|
|
182
|
+
setDocument(prev => ({ ...prev, lastSaved: new Date() }));
|
|
183
|
+
} else {
|
|
184
|
+
setSaveStatus('error');
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
setSaveStatus('error');
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const { debouncedCallback: debouncedSave, cancel: cancelSave } = useDebounce(
|
|
192
|
+
saveDocument,
|
|
193
|
+
2000
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const updateDocument = (updates: Partial<typeof document>) => {
|
|
197
|
+
const newDoc = { ...document, ...updates };
|
|
198
|
+
setDocument(newDoc);
|
|
199
|
+
debouncedSave(newDoc);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const forceSave = () => {
|
|
203
|
+
cancelSave(); // Cancel pending auto-save
|
|
204
|
+
saveDocument(document); // Save immediately
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="document-editor">
|
|
209
|
+
<div className="editor-header">
|
|
210
|
+
<Input
|
|
211
|
+
value={document.title}
|
|
212
|
+
onValueChange={(title) => updateDocument({ title })}
|
|
213
|
+
placeholder="Document title..."
|
|
214
|
+
className="title-input"
|
|
215
|
+
/>
|
|
216
|
+
|
|
217
|
+
<div className="save-status">
|
|
218
|
+
{saveStatus === 'saving' && (
|
|
219
|
+
<>
|
|
220
|
+
<Spinner size="Small" />
|
|
221
|
+
<Text type="BodySmall">Saving...</Text>
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
{saveStatus === 'saved' && (
|
|
225
|
+
<>
|
|
226
|
+
<Icon icon="Check" size="Small" />
|
|
227
|
+
<Text type="BodySmall">
|
|
228
|
+
Saved {document.lastSaved ? formatTime(document.lastSaved) : ''}
|
|
229
|
+
</Text>
|
|
230
|
+
</>
|
|
231
|
+
)}
|
|
232
|
+
{saveStatus === 'error' && (
|
|
233
|
+
<>
|
|
234
|
+
<Icon icon="Error" size="Small" />
|
|
235
|
+
<Text type="BodySmall">Save failed</Text>
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<Button size="Small" onClick={forceSave}>
|
|
241
|
+
Save Now
|
|
242
|
+
</Button>
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<TextArea
|
|
246
|
+
value={document.content}
|
|
247
|
+
onValueChange={(content) => updateDocument({ content })}
|
|
248
|
+
placeholder="Start writing..."
|
|
249
|
+
rows={20}
|
|
250
|
+
className="content-editor"
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Window Resize Handler
|
|
258
|
+
```tsx
|
|
259
|
+
function ResponsiveLayoutExample() {
|
|
260
|
+
const [windowSize, setWindowSize] = useState({
|
|
261
|
+
width: window.innerWidth,
|
|
262
|
+
height: window.innerHeight
|
|
263
|
+
});
|
|
264
|
+
const [layout, setLayout] = useState('desktop');
|
|
265
|
+
|
|
266
|
+
const updateLayout = () => {
|
|
267
|
+
const { width } = windowSize;
|
|
268
|
+
if (width < 768) {
|
|
269
|
+
setLayout('mobile');
|
|
270
|
+
} else if (width < 1024) {
|
|
271
|
+
setLayout('tablet');
|
|
272
|
+
} else {
|
|
273
|
+
setLayout('desktop');
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const handleResize = () => {
|
|
278
|
+
setWindowSize({
|
|
279
|
+
width: window.innerWidth,
|
|
280
|
+
height: window.innerHeight
|
|
281
|
+
});
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const { debouncedCallback: debouncedResize } = useDebounce(handleResize, 100);
|
|
285
|
+
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
window.addEventListener('resize', debouncedResize);
|
|
288
|
+
return () => window.removeEventListener('resize', debouncedResize);
|
|
289
|
+
}, [debouncedResize]);
|
|
290
|
+
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
updateLayout();
|
|
293
|
+
}, [windowSize]);
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div className={`layout layout-${layout}`}>
|
|
297
|
+
<div className="debug-info">
|
|
298
|
+
<Text type="BodySmall">
|
|
299
|
+
{windowSize.width} x {windowSize.height} ({layout})
|
|
300
|
+
</Text>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
303
|
+
{layout === 'mobile' && <MobileLayout />}
|
|
304
|
+
{layout === 'tablet' && <TabletLayout />}
|
|
305
|
+
{layout === 'desktop' && <DesktopLayout />}
|
|
306
|
+
</div>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### API Rate Limiting
|
|
312
|
+
```tsx
|
|
313
|
+
function RateLimitedAPIExample() {
|
|
314
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
315
|
+
const [loading, setLoading] = useState(false);
|
|
316
|
+
const [requestCount, setRequestCount] = useState(0);
|
|
317
|
+
|
|
318
|
+
const fetchSuggestions = async (query: string) => {
|
|
319
|
+
if (!query.trim()) {
|
|
320
|
+
setSuggestions([]);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
setLoading(true);
|
|
325
|
+
setRequestCount(prev => prev + 1);
|
|
326
|
+
|
|
327
|
+
try {
|
|
328
|
+
const response = await fetch(`/api/suggestions?q=${encodeURIComponent(query)}`);
|
|
329
|
+
const data = await response.json();
|
|
330
|
+
setSuggestions(data.suggestions);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('Failed to fetch suggestions:', error);
|
|
333
|
+
} finally {
|
|
334
|
+
setLoading(false);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Debounce API calls to respect rate limits
|
|
339
|
+
const { debouncedCallback: debouncedFetch } = useDebounce(fetchSuggestions, 500);
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<div>
|
|
343
|
+
<div className="api-stats">
|
|
344
|
+
<Text type="BodySmall">
|
|
345
|
+
API Requests: {requestCount}
|
|
346
|
+
</Text>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<Input
|
|
350
|
+
onValueChange={debouncedFetch}
|
|
351
|
+
placeholder="Type to get suggestions..."
|
|
352
|
+
/>
|
|
353
|
+
|
|
354
|
+
{loading && <Spinner size="Small" />}
|
|
355
|
+
|
|
356
|
+
<div className="suggestions">
|
|
357
|
+
{suggestions.map((suggestion, index) => (
|
|
358
|
+
<div key={index} className="suggestion-item">
|
|
359
|
+
{suggestion}
|
|
360
|
+
</div>
|
|
361
|
+
))}
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Bulk Operations with Progress
|
|
369
|
+
```tsx
|
|
370
|
+
function BulkOperationExample() {
|
|
371
|
+
const [selectedItems, setSelectedItems] = useState([]);
|
|
372
|
+
const [processing, setProcessing] = useState(false);
|
|
373
|
+
const [progress, setProgress] = useState(0);
|
|
374
|
+
|
|
375
|
+
const processItems = async (items: string[]) => {
|
|
376
|
+
if (items.length === 0) return;
|
|
377
|
+
|
|
378
|
+
setProcessing(true);
|
|
379
|
+
setProgress(0);
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < items.length; i++) {
|
|
382
|
+
try {
|
|
383
|
+
await fetch(`/api/process/${items[i]}`, { method: 'POST' });
|
|
384
|
+
setProgress(((i + 1) / items.length) * 100);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.error(`Failed to process item ${items[i]}:`, error);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
setProcessing(false);
|
|
391
|
+
setSelectedItems([]);
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
// Debounce bulk operations to prevent accidental multiple executions
|
|
395
|
+
const { debouncedCallback: debouncedProcess } = useDebounce(processItems, 1000);
|
|
396
|
+
|
|
397
|
+
const handleBulkAction = () => {
|
|
398
|
+
if (selectedItems.length > 0) {
|
|
399
|
+
debouncedProcess([...selectedItems]);
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
<div>
|
|
405
|
+
<div className="bulk-actions">
|
|
406
|
+
<Text type="BodyMedium">
|
|
407
|
+
{selectedItems.length} items selected
|
|
408
|
+
</Text>
|
|
409
|
+
<Button
|
|
410
|
+
onClick={handleBulkAction}
|
|
411
|
+
disabled={selectedItems.length === 0 || processing}
|
|
412
|
+
>
|
|
413
|
+
Process Selected
|
|
414
|
+
</Button>
|
|
415
|
+
</div>
|
|
416
|
+
|
|
417
|
+
{processing && (
|
|
418
|
+
<div className="progress-indicator">
|
|
419
|
+
<ProgressBar value={progress} max={100} />
|
|
420
|
+
<Text type="BodySmall">
|
|
421
|
+
Processing... {Math.round(progress)}%
|
|
422
|
+
</Text>
|
|
423
|
+
</div>
|
|
424
|
+
)}
|
|
425
|
+
|
|
426
|
+
{/* Item list with selection */}
|
|
427
|
+
<div className="item-list">
|
|
428
|
+
{items.map(item => (
|
|
429
|
+
<CheckboxItem
|
|
430
|
+
key={item.id}
|
|
431
|
+
checked={selectedItems.includes(item.id)}
|
|
432
|
+
onCheckedChange={(checked) => {
|
|
433
|
+
if (checked) {
|
|
434
|
+
setSelectedItems(prev => [...prev, item.id]);
|
|
435
|
+
} else {
|
|
436
|
+
setSelectedItems(prev => prev.filter(id => id !== item.id));
|
|
437
|
+
}
|
|
438
|
+
}}
|
|
439
|
+
>
|
|
440
|
+
{item.name}
|
|
441
|
+
</CheckboxItem>
|
|
442
|
+
))}
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Smart Notifications
|
|
450
|
+
```tsx
|
|
451
|
+
function SmartNotificationExample() {
|
|
452
|
+
const [notifications, setNotifications] = useState([]);
|
|
453
|
+
const [pendingNotification, setPendingNotification] = useState(null);
|
|
454
|
+
|
|
455
|
+
const showNotification = (message: string, type: 'info' | 'success' | 'error' = 'info') => {
|
|
456
|
+
const notification = {
|
|
457
|
+
id: Date.now(),
|
|
458
|
+
message,
|
|
459
|
+
type,
|
|
460
|
+
timestamp: new Date()
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
setNotifications(prev => [...prev, notification]);
|
|
464
|
+
|
|
465
|
+
// Auto-remove after 5 seconds
|
|
466
|
+
setTimeout(() => {
|
|
467
|
+
setNotifications(prev => prev.filter(n => n.id !== notification.id));
|
|
468
|
+
}, 5000);
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
// Debounce similar notifications to prevent spam
|
|
472
|
+
const { debouncedCallback: debouncedNotify } = useDebounce(showNotification, 1000);
|
|
473
|
+
|
|
474
|
+
const handleAction = (actionType: string) => {
|
|
475
|
+
switch (actionType) {
|
|
476
|
+
case 'save':
|
|
477
|
+
debouncedNotify('Document saved successfully', 'success');
|
|
478
|
+
break;
|
|
479
|
+
case 'error':
|
|
480
|
+
debouncedNotify('An error occurred', 'error');
|
|
481
|
+
break;
|
|
482
|
+
case 'info':
|
|
483
|
+
debouncedNotify('Information updated', 'info');
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<div>
|
|
490
|
+
<div className="action-buttons">
|
|
491
|
+
<Button onClick={() => handleAction('save')}>
|
|
492
|
+
Save (will be debounced)
|
|
493
|
+
</Button>
|
|
494
|
+
<Button onClick={() => handleAction('error')}>
|
|
495
|
+
Trigger Error
|
|
496
|
+
</Button>
|
|
497
|
+
<Button onClick={() => handleAction('info')}>
|
|
498
|
+
Show Info
|
|
499
|
+
</Button>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<div className="notifications">
|
|
503
|
+
{notifications.map(notification => (
|
|
504
|
+
<div
|
|
505
|
+
key={notification.id}
|
|
506
|
+
className={`notification notification-${notification.type}`}
|
|
507
|
+
>
|
|
508
|
+
<Text type="BodyMedium">{notification.message}</Text>
|
|
509
|
+
<Text type="BodySmall">
|
|
510
|
+
{formatTime(notification.timestamp)}
|
|
511
|
+
</Text>
|
|
512
|
+
</div>
|
|
513
|
+
))}
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Performance Benefits
|
|
521
|
+
|
|
522
|
+
1. **Reduces Function Calls**: Prevents excessive execution of expensive operations
|
|
523
|
+
2. **API Rate Limiting**: Helps comply with API rate limits and reduces server load
|
|
524
|
+
3. **UI Responsiveness**: Prevents UI blocking from frequent updates
|
|
525
|
+
4. **Memory Efficiency**: Automatic cleanup prevents memory leaks
|
|
526
|
+
5. **Bandwidth Optimization**: Reduces unnecessary network requests
|
|
527
|
+
|
|
528
|
+
## Best Practices
|
|
529
|
+
|
|
530
|
+
1. **Choose Appropriate Delays**:
|
|
531
|
+
- Search inputs: 300-500ms
|
|
532
|
+
- Form validation: 500-1000ms
|
|
533
|
+
- Auto-save: 1000-3000ms
|
|
534
|
+
- Resize handlers: 100-250ms
|
|
535
|
+
|
|
536
|
+
2. **Stable Callback References**: Use `useCallback` for callback functions to prevent unnecessary re-debouncing
|
|
537
|
+
|
|
538
|
+
3. **Cleanup on Unmount**: The hook automatically handles cleanup, but you can manually cancel if needed
|
|
539
|
+
|
|
540
|
+
4. **Error Handling**: Always wrap debounced API calls in try-catch blocks
|
|
541
|
+
|
|
542
|
+
## Common Patterns
|
|
543
|
+
|
|
544
|
+
### Search Input
|
|
545
|
+
```tsx
|
|
546
|
+
const { debouncedCallback: debouncedSearch } = useDebounce(searchFunction, 300);
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Form Validation
|
|
550
|
+
```tsx
|
|
551
|
+
const { debouncedCallback: debouncedValidate } = useDebounce(validateForm, 500);
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Auto-save
|
|
555
|
+
```tsx
|
|
556
|
+
const { debouncedCallback: debouncedSave, cancel: cancelSave } = useDebounce(saveDocument, 2000);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
## TypeScript Support
|
|
560
|
+
|
|
561
|
+
The hook is fully typed and preserves the original function signature:
|
|
562
|
+
|
|
563
|
+
```tsx
|
|
564
|
+
const myFunction = (a: string, b: number) => console.log(a, b);
|
|
565
|
+
const { debouncedCallback } = useDebounce(myFunction, 500);
|
|
566
|
+
|
|
567
|
+
// TypeScript knows the parameters are (a: string, b: number)
|
|
568
|
+
debouncedCallback('hello', 42); // ✅ Correctly typed
|
|
569
|
+
debouncedCallback(42, 'hello'); // ❌ TypeScript error
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
## Related Hooks
|
|
573
|
+
|
|
574
|
+
- **[React.useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)** - Memoization for functions
|
|
575
|
+
- **[React.useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect)** - Side effects management
|
|
576
|
+
- **[Search Component](../molecules/Search.md)** - Uses useDebounce internally
|
package/package.json
CHANGED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* Return type for the useInflateView hook.
|
|
4
|
-
*/
|
|
5
|
-
export type UseInflateViewReturn = ReactElement;
|
|
6
|
-
/**
|
|
7
|
-
* Parameters for the useInflateView hook.
|
|
8
|
-
*/
|
|
9
|
-
export type UseInflateViewParams<T extends Record<string, unknown>> = {
|
|
10
|
-
Component: ComponentType<T>;
|
|
11
|
-
props: T;
|
|
12
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement } from 'react';
|
|
2
|
-
/**
|
|
3
|
-
* Return type for the useInflateView hook.
|
|
4
|
-
*/
|
|
5
|
-
export type UseInflateViewReturn = ReactElement;
|
|
6
|
-
/**
|
|
7
|
-
* Parameters for the useInflateView hook.
|
|
8
|
-
*/
|
|
9
|
-
export type UseInflateViewParams<T extends Record<string, unknown>> = {
|
|
10
|
-
Component: ComponentType<T>;
|
|
11
|
-
props: T;
|
|
12
|
-
};
|