0
# Async Data Loading
1
2
React-Select provides powerful async data loading capabilities through AsyncSelect and AsyncCreatableSelect components, along with the useAsync hook for custom implementations. This enables dynamic option fetching, caching, and loading state management.
3
4
## Capabilities
5
6
### AsyncSelect Component
7
8
Select component with built-in async option loading, caching, and loading state management.
9
10
```typescript { .api }
11
/**
12
* Select component with async option loading capabilities
13
* @param props - Standard Props plus AsyncProps for async functionality
14
* @returns JSX.Element with async loading capabilities
15
*/
16
function AsyncSelect<
17
Option = unknown,
18
IsMulti extends boolean = false,
19
Group extends GroupBase<Option> = GroupBase<Option>
20
>(props: Props<Option, IsMulti, Group> & AsyncProps<Option>): JSX.Element;
21
22
interface AsyncProps<Option> {
23
/** Default options to show before any input, or true to load options immediately */
24
defaultOptions?: OptionsOrGroups<Option, Group> | boolean;
25
/** Enable caching of loaded options to avoid repeated API calls */
26
cacheOptions?: any;
27
/**
28
* Function to load options asynchronously based on input value
29
* Can return Promise or use callback pattern
30
*/
31
loadOptions?: (
32
inputValue: string,
33
callback: (options: OptionsOrGroups<Option, Group>) => void
34
) => Promise<OptionsOrGroups<Option, Group>> | void;
35
}
36
```
37
38
**Usage Examples:**
39
40
```typescript
41
import AsyncSelect from "react-select/async";
42
43
// Promise-based async loading
44
const PromiseAsyncSelect = () => {
45
const loadOptions = async (inputValue: string): Promise<Option[]> => {
46
const response = await fetch(`/api/search?q=${inputValue}`);
47
const data = await response.json();
48
return data.map((item: any) => ({
49
value: item.id,
50
label: item.name,
51
}));
52
};
53
54
return (
55
<AsyncSelect
56
cacheOptions
57
defaultOptions
58
loadOptions={loadOptions}
59
placeholder="Type to search..."
60
noOptionsMessage={({ inputValue }) =>
61
inputValue ? `No results for "${inputValue}"` : "Type to search"
62
}
63
/>
64
);
65
};
66
67
// Callback-based async loading
68
const CallbackAsyncSelect = () => {
69
const loadOptions = (inputValue: string, callback: Function) => {
70
if (!inputValue) {
71
callback([]);
72
return;
73
}
74
75
setTimeout(() => {
76
const filteredOptions = allOptions.filter((option) =>
77
option.label.toLowerCase().includes(inputValue.toLowerCase())
78
);
79
callback(filteredOptions);
80
}, 1000);
81
};
82
83
return (
84
<AsyncSelect
85
loadOptions={loadOptions}
86
placeholder="Type to search..."
87
cacheOptions
88
/>
89
);
90
};
91
92
// With error handling
93
const ErrorHandlingAsync = () => {
94
const [isLoading, setIsLoading] = useState(false);
95
const [error, setError] = useState<string | null>(null);
96
97
const loadOptions = async (inputValue: string) => {
98
if (!inputValue) return [];
99
100
setIsLoading(true);
101
setError(null);
102
103
try {
104
const response = await api.searchUsers(inputValue);
105
return response.data;
106
} catch (err) {
107
setError('Failed to load options');
108
return [];
109
} finally {
110
setIsLoading(false);
111
}
112
};
113
114
return (
115
<div>
116
<AsyncSelect
117
loadOptions={loadOptions}
118
isLoading={isLoading}
119
placeholder={error ? error : "Search users..."}
120
/>
121
</div>
122
);
123
};
124
```
125
126
### Default Options Configuration
127
128
Control initial option loading behavior and caching strategies.
129
130
```typescript { .api }
131
/**
132
* defaultOptions configuration for AsyncSelect
133
*/
134
type DefaultOptions<Option, Group extends GroupBase<Option>> =
135
| OptionsOrGroups<Option, Group> // Specific default options
136
| boolean; // true = load immediately, false = no defaults
137
138
/**
139
* cacheOptions configuration for result caching
140
*/
141
type CacheOptions = any; // Enable/disable caching of loadOptions results
142
```
143
144
**Usage Examples:**
145
146
```typescript
147
// Preload specific default options
148
const PreloadedAsync = () => (
149
<AsyncSelect
150
defaultOptions={[
151
{ value: 'popular1', label: 'Popular Option 1' },
152
{ value: 'popular2', label: 'Popular Option 2' },
153
]}
154
loadOptions={loadOptions}
155
/>
156
);
157
158
// Auto-load on mount
159
const AutoLoadAsync = () => (
160
<AsyncSelect
161
defaultOptions={true} // Calls loadOptions('') on mount
162
loadOptions={loadOptions}
163
cacheOptions={true} // Cache results
164
/>
165
);
166
167
// No defaults, load only on input
168
const OnDemandAsync = () => (
169
<AsyncSelect
170
defaultOptions={false} // No initial options
171
loadOptions={loadOptions}
172
placeholder="Start typing to search..."
173
/>
174
);
175
```
176
177
### Load Options Function Patterns
178
179
Different patterns for implementing the loadOptions function.
180
181
```typescript { .api }
182
/**
183
* Load options function - Promise pattern
184
* @param inputValue - Current input value for filtering
185
* @returns Promise resolving to options array
186
*/
187
type LoadOptionsPromise<Option, Group extends GroupBase<Option>> = (
188
inputValue: string
189
) => Promise<OptionsOrGroups<Option, Group>>;
190
191
/**
192
* Load options function - Callback pattern
193
* @param inputValue - Current input value for filtering
194
* @param callback - Function to call with loaded options
195
*/
196
type LoadOptionsCallback<Option, Group extends GroupBase<Option>> = (
197
inputValue: string,
198
callback: (options: OptionsOrGroups<Option, Group>) => void
199
) => void;
200
```
201
202
**Usage Examples:**
203
204
```typescript
205
// REST API integration
206
const restApiLoader = async (inputValue: string) => {
207
const params = new URLSearchParams({
208
search: inputValue,
209
limit: '20',
210
});
211
212
const response = await fetch(`/api/options?${params}`);
213
if (!response.ok) throw new Error('Failed to fetch');
214
215
const data = await response.json();
216
return data.items.map((item: any) => ({
217
value: item.id,
218
label: item.displayName,
219
...item,
220
}));
221
};
222
223
// GraphQL integration
224
const graphqlLoader = async (inputValue: string) => {
225
const query = `
226
query SearchOptions($search: String!) {
227
searchOptions(search: $search) {
228
id
229
name
230
description
231
}
232
}
233
`;
234
235
const response = await graphqlClient.query({
236
query,
237
variables: { search: inputValue },
238
});
239
240
return response.data.searchOptions.map((item: any) => ({
241
value: item.id,
242
label: item.name,
243
meta: item.description,
244
}));
245
};
246
247
// Debounced loading
248
const debouncedLoader = useMemo(
249
() => debounce(async (inputValue: string) => {
250
return await api.search(inputValue);
251
}, 300),
252
[]
253
);
254
255
// Grouped options loading
256
const groupedLoader = async (inputValue: string) => {
257
const [users, teams] = await Promise.all([
258
api.searchUsers(inputValue),
259
api.searchTeams(inputValue),
260
]);
261
262
return [
263
{
264
label: 'Users',
265
options: users.map(user => ({ value: user.id, label: user.name })),
266
},
267
{
268
label: 'Teams',
269
options: teams.map(team => ({ value: team.id, label: team.name })),
270
},
271
];
272
};
273
```
274
275
### useAsync Hook
276
277
Hook for implementing custom async behavior with select components.
278
279
```typescript { .api }
280
/**
281
* Hook for managing async select state and behavior
282
* @param props - Async hook configuration
283
* @returns Object with async state and handlers
284
*/
285
function useAsync<Option, Group extends GroupBase<Option>>(
286
props: UseAsyncProps<Option, Group>
287
): UseAsyncResult<Option, Group>;
288
289
interface UseAsyncProps<Option, Group extends GroupBase<Option>> {
290
defaultOptions?: OptionsOrGroups<Option, Group> | boolean;
291
cacheOptions?: any;
292
loadOptions?: LoadOptionsFunction<Option, Group>;
293
}
294
295
interface UseAsyncResult<Option, Group extends GroupBase<Option>> {
296
/** Current loaded options */
297
options: OptionsOrGroups<Option, Group>;
298
/** Whether async loading is in progress */
299
isLoading: boolean;
300
/** Current input value being searched */
301
inputValue: string;
302
/** Handler for input changes that triggers loading */
303
onInputChange: (inputValue: string, actionMeta: InputActionMeta) => void;
304
/** Handler for menu open events */
305
onMenuOpen: () => void;
306
}
307
```
308
309
**Usage Examples:**
310
311
```typescript
312
import { BaseSelect, useAsync } from "react-select";
313
314
// Custom async select with additional logic
315
const CustomAsyncSelect = (props) => {
316
const asyncProps = useAsync({
317
defaultOptions: true,
318
cacheOptions: true,
319
loadOptions: props.loadOptions,
320
});
321
322
return (
323
<BaseSelect
324
{...props}
325
{...asyncProps}
326
filterOption={null} // Disable client-side filtering
327
/>
328
);
329
};
330
331
// Async with custom loading states
332
const EnhancedAsyncSelect = () => {
333
const [error, setError] = useState(null);
334
335
const loadOptions = async (inputValue: string) => {
336
try {
337
setError(null);
338
return await api.search(inputValue);
339
} catch (err) {
340
setError(err.message);
341
return [];
342
}
343
};
344
345
const asyncProps = useAsync({
346
loadOptions,
347
cacheOptions: true,
348
});
349
350
return (
351
<div>
352
{error && <div className="error">{error}</div>}
353
<BaseSelect
354
{...asyncProps}
355
loadingMessage={() => "Searching..."}
356
noOptionsMessage={({ inputValue }) =>
357
error ? "Error loading options" : `No results for "${inputValue}"`
358
}
359
/>
360
</div>
361
);
362
};
363
```
364
365
### AsyncCreatableSelect Integration
366
367
Combining async loading with option creation capabilities.
368
369
```typescript { .api }
370
/**
371
* Select combining async loading and option creation
372
* @param props - Combined async and creatable props
373
* @returns JSX.Element with both capabilities
374
*/
375
function AsyncCreatableSelect<
376
Option = unknown,
377
IsMulti extends boolean = false,
378
Group extends GroupBase<Option> = GroupBase<Option>
379
>(
380
props: Props<Option, IsMulti, Group> & AsyncProps<Option> & CreatableProps<Option>
381
): JSX.Element;
382
383
interface AsyncCreatableProps<Option> extends AsyncProps<Option>, CreatableProps<Option> {
384
/** Allow creating options while async loading is in progress */
385
allowCreateWhileLoading?: boolean;
386
}
387
```
388
389
**Usage Examples:**
390
391
```typescript
392
import AsyncCreatableSelect from "react-select/async-creatable";
393
394
// Tags with async loading and creation
395
const TagsAsyncCreatable = () => {
396
const loadOptions = async (inputValue: string) => {
397
const response = await api.searchTags(inputValue);
398
return response.map(tag => ({ value: tag.id, label: tag.name }));
399
};
400
401
const handleCreate = async (inputValue: string) => {
402
const newTag = await api.createTag({ name: inputValue });
403
return { value: newTag.id, label: newTag.name };
404
};
405
406
return (
407
<AsyncCreatableSelect
408
isMulti
409
cacheOptions
410
defaultOptions={false}
411
loadOptions={loadOptions}
412
onCreateOption={handleCreate}
413
allowCreateWhileLoading={true}
414
formatCreateLabel={(inputValue) => `Create tag "${inputValue}"`}
415
noOptionsMessage={({ inputValue }) =>
416
inputValue ? `No existing tags found. Type to create "${inputValue}"` : "Start typing to search tags"
417
}
418
/>
419
);
420
};
421
```
422
423
## Performance Optimization
424
425
### Caching Strategies
426
427
```typescript
428
// Memory-efficient caching with size limits
429
const createCachedLoader = (maxCacheSize = 100) => {
430
const cache = new Map();
431
432
return async (inputValue: string) => {
433
if (cache.has(inputValue)) {
434
return cache.get(inputValue);
435
}
436
437
const result = await api.search(inputValue);
438
439
// Implement LRU cache
440
if (cache.size >= maxCacheSize) {
441
const firstKey = cache.keys().next().value;
442
cache.delete(firstKey);
443
}
444
445
cache.set(inputValue, result);
446
return result;
447
};
448
};
449
450
// Time-based cache expiration
451
const createTimeCachedLoader = (ttlMs = 300000) => { // 5 minutes
452
const cache = new Map();
453
454
return async (inputValue: string) => {
455
const cached = cache.get(inputValue);
456
if (cached && Date.now() - cached.timestamp < ttlMs) {
457
return cached.data;
458
}
459
460
const result = await api.search(inputValue);
461
cache.set(inputValue, {
462
data: result,
463
timestamp: Date.now(),
464
});
465
466
return result;
467
};
468
};
469
```
470
471
### Debouncing and Throttling
472
473
```typescript
474
// Debounced loading to reduce API calls
475
const useDebouncedAsync = (loadOptions: Function, delay = 300) => {
476
const debouncedLoader = useMemo(
477
() => debounce(loadOptions, delay),
478
[loadOptions, delay]
479
);
480
481
return debouncedLoader;
482
};
483
484
// Usage
485
const DebouncedAsyncSelect = () => {
486
const baseLoader = async (inputValue: string) => {
487
return await api.search(inputValue);
488
};
489
490
const debouncedLoader = useDebouncedAsync(baseLoader, 500);
491
492
return (
493
<AsyncSelect
494
loadOptions={debouncedLoader}
495
cacheOptions
496
/>
497
);
498
};
499
```