0
# React Native User Interaction APIs
1
2
React Native provides comprehensive APIs for handling user interactions, displaying system dialogs, accessing device feedback capabilities, and platform-specific UI components.
3
4
## Installation
5
6
```bash
7
npm install react-native
8
```
9
10
## System Dialogs
11
12
### Alert
13
14
Display native alert dialogs with customizable buttons and actions.
15
16
```javascript { .api }
17
// ESM
18
import {Alert} from 'react-native';
19
20
// CommonJS
21
const {Alert} = require('react-native');
22
23
// Basic alert
24
Alert.alert('Alert Title', 'Alert message');
25
26
// Alert with single button
27
Alert.alert(
28
'Confirmation',
29
'Are you sure you want to continue?',
30
[{text: 'OK', onPress: () => console.log('OK pressed')}]
31
);
32
33
// Alert with multiple buttons
34
Alert.alert(
35
'Save Changes',
36
'Do you want to save your changes before leaving?',
37
[
38
{text: 'Cancel', style: 'cancel'},
39
{text: 'Don\'t Save', style: 'destructive', onPress: () => discardChanges()},
40
{text: 'Save', onPress: () => saveChanges()},
41
]
42
);
43
44
// Alert with custom button styles
45
Alert.alert(
46
'Delete Item',
47
'This action cannot be undone.',
48
[
49
{text: 'Cancel', style: 'cancel'},
50
{
51
text: 'Delete',
52
style: 'destructive',
53
onPress: () => deleteItem(),
54
},
55
],
56
{
57
cancelable: true, // Android: allow dismissing by tapping outside
58
onDismiss: () => console.log('Alert dismissed'),
59
}
60
);
61
62
// Prompt for text input (iOS only)
63
Alert.prompt(
64
'Enter Name',
65
'Please enter your name:',
66
[
67
{text: 'Cancel', style: 'cancel'},
68
{
69
text: 'OK',
70
onPress: (text) => {
71
console.log('Entered text:', text);
72
setUserName(text);
73
},
74
},
75
],
76
'plain-text', // Input type
77
'Default text' // Default value
78
);
79
80
// Secure text prompt (iOS only)
81
Alert.prompt(
82
'Enter Password',
83
'Please enter your password:',
84
[
85
{text: 'Cancel', style: 'cancel'},
86
{text: 'Login', onPress: (password) => login(password)},
87
],
88
'secure-text'
89
);
90
91
// Login prompt with username and password (iOS only)
92
Alert.prompt(
93
'Login',
94
'Please enter your credentials:',
95
[
96
{text: 'Cancel', style: 'cancel'},
97
{
98
text: 'Login',
99
onPress: (username, password) => {
100
login(username, password);
101
},
102
},
103
],
104
'login-password'
105
);
106
107
// Async alert with promise
108
const showAsyncAlert = () => {
109
return new Promise((resolve) => {
110
Alert.alert(
111
'Confirmation',
112
'Do you want to proceed?',
113
[
114
{text: 'No', onPress: () => resolve(false)},
115
{text: 'Yes', onPress: () => resolve(true)},
116
]
117
);
118
});
119
};
120
121
// Usage
122
const handleAction = async () => {
123
const confirmed = await showAsyncAlert();
124
if (confirmed) {
125
performAction();
126
}
127
};
128
129
// Custom alert hook
130
function useAlert() {
131
const showAlert = useCallback((title, message, buttons = []) => {
132
Alert.alert(title, message, buttons);
133
}, []);
134
135
const showConfirmation = useCallback((title, message, onConfirm, onCancel) => {
136
Alert.alert(title, message, [
137
{text: 'Cancel', style: 'cancel', onPress: onCancel},
138
{text: 'OK', onPress: onConfirm},
139
]);
140
}, []);
141
142
const showError = useCallback((message) => {
143
Alert.alert('Error', message, [{text: 'OK'}]);
144
}, []);
145
146
const showSuccess = useCallback((message) => {
147
Alert.alert('Success', message, [{text: 'OK'}]);
148
}, []);
149
150
return {
151
showAlert,
152
showConfirmation,
153
showError,
154
showSuccess,
155
};
156
}
157
```
158
159
```typescript { .api }
160
interface AlertStatic {
161
// Basic alert
162
alert(
163
title: string,
164
message?: string,
165
buttons?: AlertButton[],
166
options?: AlertOptions
167
): void;
168
169
// Text input prompt (iOS only)
170
prompt(
171
title: string,
172
message?: string,
173
callbackOrButtons?: ((text: string) => void) | AlertButton[],
174
type?: AlertType,
175
defaultValue?: string,
176
keyboardType?: string
177
): void;
178
}
179
180
interface AlertButton {
181
text?: string;
182
onPress?: (value?: string) => void;
183
style?: 'default' | 'cancel' | 'destructive';
184
}
185
186
interface AlertOptions {
187
cancelable?: boolean; // Android only
188
onDismiss?: () => void; // Android only
189
userInterfaceStyle?: 'unspecified' | 'light' | 'dark'; // iOS only
190
}
191
192
type AlertType = 'default' | 'plain-text' | 'secure-text' | 'login-password';
193
```
194
195
## Device Feedback APIs
196
197
### Vibration
198
199
Trigger device vibration patterns for haptic feedback.
200
201
```javascript { .api }
202
import {Vibration} from 'react-native';
203
204
// Basic vibration (400ms on iOS, default pattern on Android)
205
Vibration.vibrate();
206
207
// Custom duration (iOS only, Android uses default)
208
Vibration.vibrate(1000); // 1 second
209
210
// Vibration pattern [delay, vibrate, delay, vibrate, ...]
211
const pattern = [0, 1000, 1000, 1000]; // Vibrate 1s, pause 1s, vibrate 1s
212
Vibration.vibrate(pattern);
213
214
// Repeat vibration pattern
215
Vibration.vibrate(pattern, true); // Repeat infinitely
216
217
// Stop vibration
218
Vibration.cancel();
219
220
// Predefined patterns
221
const vibrationPatterns = {
222
short: [0, 200],
223
medium: [0, 500],
224
long: [0, 1000],
225
double: [0, 200, 100, 200],
226
triple: [0, 200, 100, 200, 100, 200],
227
heartbeat: [0, 50, 50, 50, 50, 400, 50, 400],
228
sos: [0, 200, 100, 200, 100, 200, 200, 400, 100, 400, 100, 400, 200, 200, 100, 200, 100, 200],
229
};
230
231
// Vibration feedback component
232
function VibrationFeedback({pattern = 'short', children, onPress}) {
233
const handlePress = () => {
234
Vibration.vibrate(vibrationPatterns[pattern]);
235
onPress?.();
236
};
237
238
return (
239
<TouchableOpacity onPress={handlePress}>
240
{children}
241
</TouchableOpacity>
242
);
243
}
244
245
// Haptic feedback for different interactions
246
const hapticFeedback = {
247
success: () => Vibration.vibrate([0, 50, 50, 50]),
248
error: () => Vibration.vibrate([0, 100, 100, 100, 100, 100]),
249
warning: () => Vibration.vibrate([0, 200, 100, 200]),
250
selection: () => Vibration.vibrate([0, 10]),
251
impact: () => Vibration.vibrate([0, 30]),
252
notification: () => Vibration.vibrate([0, 50, 50, 100]),
253
};
254
255
// Form validation with haptic feedback
256
function FormWithHaptics() {
257
const [email, setEmail] = useState('');
258
const [error, setError] = useState('');
259
260
const validateEmail = (value) => {
261
if (!value.includes('@')) {
262
setError('Invalid email');
263
hapticFeedback.error();
264
} else {
265
setError('');
266
hapticFeedback.success();
267
}
268
};
269
270
return (
271
<View>
272
<TextInput
273
value={email}
274
onChangeText={setEmail}
275
onBlur={() => validateEmail(email)}
276
placeholder="Email"
277
/>
278
{error && <Text style={styles.error}>{error}</Text>}
279
</View>
280
);
281
}
282
283
// Game interactions with vibration
284
function GameButton({onPress, disabled}) {
285
const handlePress = () => {
286
if (disabled) {
287
hapticFeedback.error();
288
return;
289
}
290
291
hapticFeedback.impact();
292
onPress();
293
};
294
295
return (
296
<TouchableOpacity onPress={handlePress} disabled={disabled}>
297
<Text>Game Action</Text>
298
</TouchableOpacity>
299
);
300
}
301
302
// Settings for vibration preferences
303
function VibrationSettings() {
304
const [vibrationEnabled, setVibrationEnabled] = useState(true);
305
306
const toggleVibration = (enabled) => {
307
setVibrationEnabled(enabled);
308
309
if (enabled) {
310
hapticFeedback.success();
311
}
312
};
313
314
return (
315
<View style={styles.settingRow}>
316
<Text>Enable Vibration</Text>
317
<Switch value={vibrationEnabled} onValueChange={toggleVibration} />
318
</View>
319
);
320
}
321
```
322
323
```typescript { .api }
324
interface VibrationStatic {
325
// Trigger vibration
326
vibrate(pattern?: number | number[], repeat?: boolean): void;
327
328
// Stop vibration
329
cancel(): void;
330
}
331
```
332
333
## Platform-Specific UI Components
334
335
### ActionSheetIOS
336
337
Display native iOS action sheets with customizable options.
338
339
```javascript { .api }
340
import {ActionSheetIOS} from 'react-native';
341
342
// Basic action sheet (iOS only)
343
const showActionSheet = () => {
344
ActionSheetIOS.showActionSheetWithOptions(
345
{
346
title: 'Choose an option',
347
message: 'Select one of the following options:',
348
options: ['Cancel', 'Option 1', 'Option 2', 'Option 3'],
349
cancelButtonIndex: 0,
350
},
351
(buttonIndex) => {
352
if (buttonIndex === 1) {
353
console.log('Option 1 selected');
354
} else if (buttonIndex === 2) {
355
console.log('Option 2 selected');
356
} else if (buttonIndex === 3) {
357
console.log('Option 3 selected');
358
}
359
}
360
);
361
};
362
363
// Action sheet with destructive option
364
const showDestructiveActionSheet = () => {
365
ActionSheetIOS.showActionSheetWithOptions(
366
{
367
title: 'Delete Item',
368
message: 'This action cannot be undone',
369
options: ['Cancel', 'Delete Item'],
370
destructiveButtonIndex: 1,
371
cancelButtonIndex: 0,
372
},
373
(buttonIndex) => {
374
if (buttonIndex === 1) {
375
deleteItem();
376
}
377
}
378
);
379
};
380
381
// Share action sheet
382
const showShareSheet = () => {
383
ActionSheetIOS.showShareActionSheetWithOptions(
384
{
385
url: 'https://example.com',
386
message: 'Check out this amazing app!',
387
subject: 'App Recommendation', // Email subject
388
},
389
(error) => {
390
console.error('Share failed:', error);
391
},
392
(success, method) => {
393
if (success) {
394
console.log(`Shared via ${method}`);
395
}
396
}
397
);
398
};
399
400
// Photo selection action sheet
401
const showPhotoActionSheet = () => {
402
ActionSheetIOS.showActionSheetWithOptions(
403
{
404
title: 'Select Photo',
405
options: ['Cancel', 'Camera', 'Photo Library'],
406
cancelButtonIndex: 0,
407
},
408
(buttonIndex) => {
409
switch (buttonIndex) {
410
case 1:
411
openCamera();
412
break;
413
case 2:
414
openPhotoLibrary();
415
break;
416
}
417
}
418
);
419
};
420
421
// Custom hook for action sheets
422
function useActionSheet() {
423
const showOptions = useCallback((options, onSelect) => {
424
if (Platform.OS !== 'ios') {
425
// Fallback for non-iOS platforms
426
Alert.alert(
427
options.title,
428
options.message,
429
options.options.map((option, index) => ({
430
text: option,
431
onPress: () => onSelect(index),
432
style: index === options.cancelButtonIndex ? 'cancel' :
433
index === options.destructiveButtonIndex ? 'destructive' : 'default',
434
}))
435
);
436
return;
437
}
438
439
ActionSheetIOS.showActionSheetWithOptions(options, onSelect);
440
}, []);
441
442
return {showOptions};
443
}
444
445
// Settings menu with action sheet
446
function SettingsMenu() {
447
const {showOptions} = useActionSheet();
448
449
const showSettingsActions = () => {
450
showOptions(
451
{
452
title: 'Settings',
453
options: ['Cancel', 'Edit Profile', 'Privacy Settings', 'Sign Out'],
454
cancelButtonIndex: 0,
455
destructiveButtonIndex: 3,
456
},
457
(buttonIndex) => {
458
switch (buttonIndex) {
459
case 1:
460
navigation.navigate('EditProfile');
461
break;
462
case 2:
463
navigation.navigate('PrivacySettings');
464
break;
465
case 3:
466
signOut();
467
break;
468
}
469
}
470
);
471
};
472
473
return (
474
<TouchableOpacity onPress={showSettingsActions}>
475
<Text>Settings</Text>
476
</TouchableOpacity>
477
);
478
}
479
```
480
481
```typescript { .api }
482
interface ActionSheetIOSStatic {
483
// Show action sheet
484
showActionSheetWithOptions(
485
options: ActionSheetOptions,
486
callback: (buttonIndex: number) => void
487
): void;
488
489
// Show share sheet (iOS only)
490
showShareActionSheetWithOptions(
491
options: ShareActionSheetOptions,
492
failureCallback: (error: Error) => void,
493
successCallback: (success: boolean, method: string) => void
494
): void;
495
}
496
497
interface ActionSheetOptions {
498
title?: string;
499
message?: string;
500
options: string[];
501
cancelButtonIndex?: number;
502
destructiveButtonIndex?: number | number[];
503
anchor?: number; // iPad popover anchor
504
tintColor?: string;
505
userInterfaceStyle?: 'light' | 'dark';
506
}
507
508
interface ShareActionSheetOptions {
509
message?: string;
510
url?: string;
511
subject?: string;
512
anchor?: number; // iPad popover anchor
513
tintColor?: string;
514
excludedActivityTypes?: string[];
515
}
516
```
517
518
### ToastAndroid
519
520
Display native Android toast messages for brief notifications.
521
522
```javascript { .api }
523
import {ToastAndroid} from 'react-native';
524
525
// Basic toast (Android only)
526
const showToast = (message) => {
527
if (Platform.OS === 'android') {
528
ToastAndroid.show(message, ToastAndroid.SHORT);
529
}
530
};
531
532
// Different toast durations
533
const showShortToast = (message) => {
534
ToastAndroid.show(message, ToastAndroid.SHORT); // ~2 seconds
535
};
536
537
const showLongToast = (message) => {
538
ToastAndroid.show(message, ToastAndroid.LONG); // ~3.5 seconds
539
};
540
541
// Toast with gravity positioning
542
const showPositionedToast = (message) => {
543
ToastAndroid.showWithGravity(
544
message,
545
ToastAndroid.SHORT,
546
ToastAndroid.CENTER
547
);
548
};
549
550
// Toast with custom positioning
551
const showCustomPositionToast = (message) => {
552
ToastAndroid.showWithGravityAndOffset(
553
message,
554
ToastAndroid.SHORT,
555
ToastAndroid.BOTTOM,
556
25, // x offset
557
50 // y offset
558
);
559
};
560
561
// Cross-platform toast utility
562
const toast = {
563
show: (message, duration = 'short') => {
564
if (Platform.OS === 'android') {
565
const androidDuration = duration === 'long'
566
? ToastAndroid.LONG
567
: ToastAndroid.SHORT;
568
ToastAndroid.show(message, androidDuration);
569
} else {
570
// iOS fallback using Alert
571
Alert.alert('', message);
572
}
573
},
574
575
success: (message) => {
576
toast.show(`✅ ${message}`);
577
},
578
579
error: (message) => {
580
toast.show(`❌ ${message}`);
581
},
582
583
warning: (message) => {
584
toast.show(`⚠️ ${message}`);
585
},
586
587
info: (message) => {
588
toast.show(`ℹ️ ${message}`);
589
},
590
};
591
592
// Form submission with toast feedback
593
function FormWithToast() {
594
const [loading, setLoading] = useState(false);
595
596
const handleSubmit = async (formData) => {
597
setLoading(true);
598
599
try {
600
await submitForm(formData);
601
toast.success('Form submitted successfully!');
602
} catch (error) {
603
toast.error('Failed to submit form');
604
} finally {
605
setLoading(false);
606
}
607
};
608
609
return (
610
<View>
611
{/* Form content */}
612
<Button
613
title="Submit"
614
onPress={handleSubmit}
615
disabled={loading}
616
/>
617
</View>
618
);
619
}
620
621
// Network status with toast
622
function NetworkStatusToast() {
623
useEffect(() => {
624
const unsubscribe = NetInfo.addEventListener(state => {
625
if (!state.isConnected) {
626
toast.warning('No internet connection');
627
} else if (state.isConnected) {
628
toast.success('Connected to internet');
629
}
630
});
631
632
return unsubscribe;
633
}, []);
634
635
return null;
636
}
637
638
// Custom toast hook
639
function useToast() {
640
const show = useCallback((message, options = {}) => {
641
if (Platform.OS === 'android') {
642
const duration = options.duration === 'long'
643
? ToastAndroid.LONG
644
: ToastAndroid.SHORT;
645
646
const gravity = options.position === 'top'
647
? ToastAndroid.TOP
648
: options.position === 'center'
649
? ToastAndroid.CENTER
650
: ToastAndroid.BOTTOM;
651
652
ToastAndroid.showWithGravity(message, duration, gravity);
653
} else {
654
// iOS fallback
655
Alert.alert('', message);
656
}
657
}, []);
658
659
return {
660
show,
661
success: (message) => show(`✅ ${message}`),
662
error: (message) => show(`❌ ${message}`),
663
warning: (message) => show(`⚠️ ${message}`),
664
info: (message) => show(`ℹ️ ${message}`),
665
};
666
}
667
```
668
669
```typescript { .api }
670
interface ToastAndroidStatic {
671
// Constants
672
SHORT: number;
673
LONG: number;
674
TOP: number;
675
BOTTOM: number;
676
CENTER: number;
677
678
// Show methods
679
show(message: string, duration: number): void;
680
showWithGravity(message: string, duration: number, gravity: number): void;
681
showWithGravityAndOffset(
682
message: string,
683
duration: number,
684
gravity: number,
685
xOffset: number,
686
yOffset: number
687
): void;
688
}
689
```
690
691
## Accessibility and Interaction Helpers
692
693
### Accessibility Features
694
695
```javascript { .api }
696
// Accessibility-aware interactions
697
function AccessibleButton({onPress, children, accessibilityLabel, accessibilityHint}) {
698
return (
699
<TouchableOpacity
700
onPress={onPress}
701
accessible={true}
702
accessibilityRole="button"
703
accessibilityLabel={accessibilityLabel}
704
accessibilityHint={accessibilityHint}
705
accessibilityState={{disabled: false}}
706
>
707
{children}
708
</TouchableOpacity>
709
);
710
}
711
712
// Screen reader announcements
713
function AccessibilityAnnouncements() {
714
const announce = (message) => {
715
// Announce message to screen readers
716
AccessibilityInfo.announceForAccessibility(message);
717
};
718
719
const handleSave = () => {
720
// Perform save operation
721
saveData().then(() => {
722
announce('Data saved successfully');
723
}).catch(() => {
724
announce('Failed to save data');
725
});
726
};
727
728
return (
729
<Button
730
title="Save"
731
onPress={handleSave}
732
accessibilityLabel="Save data"
733
accessibilityHint="Saves your current progress"
734
/>
735
);
736
}
737
738
// Accessibility info detection
739
function AccessibilityInfo() {
740
const [isScreenReaderEnabled, setScreenReaderEnabled] = useState(false);
741
const [isReduceMotionEnabled, setReduceMotionEnabled] = useState(false);
742
743
useEffect(() => {
744
// Check screen reader status
745
AccessibilityInfo.isScreenReaderEnabled().then(setScreenReaderEnabled);
746
747
// Check reduce motion preference
748
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotionEnabled);
749
750
// Listen for changes
751
const screenReaderSubscription = AccessibilityInfo.addEventListener(
752
'screenReaderChanged',
753
setScreenReaderEnabled
754
);
755
756
const reduceMotionSubscription = AccessibilityInfo.addEventListener(
757
'reduceMotionChanged',
758
setReduceMotionEnabled
759
);
760
761
return () => {
762
screenReaderSubscription.remove();
763
reduceMotionSubscription.remove();
764
};
765
}, []);
766
767
return (
768
<View>
769
<Text>Screen Reader: {isScreenReaderEnabled ? 'Enabled' : 'Disabled'}</Text>
770
<Text>Reduce Motion: {isReduceMotionEnabled ? 'Enabled' : 'Disabled'}</Text>
771
</View>
772
);
773
}
774
```
775
776
## Interaction Patterns
777
778
### Long Press and Context Menus
779
780
```javascript { .api }
781
// Long press interactions
782
function LongPressItem({item, onEdit, onDelete}) {
783
const showContextMenu = () => {
784
if (Platform.OS === 'ios') {
785
ActionSheetIOS.showActionSheetWithOptions(
786
{
787
options: ['Cancel', 'Edit', 'Delete'],
788
destructiveButtonIndex: 2,
789
cancelButtonIndex: 0,
790
},
791
(buttonIndex) => {
792
if (buttonIndex === 1) onEdit(item);
793
if (buttonIndex === 2) onDelete(item);
794
}
795
);
796
} else {
797
Alert.alert('Context Menu', 'Choose an action', [
798
{text: 'Cancel'},
799
{text: 'Edit', onPress: () => onEdit(item)},
800
{text: 'Delete', style: 'destructive', onPress: () => onDelete(item)},
801
]);
802
}
803
};
804
805
return (
806
<TouchableOpacity onLongPress={showContextMenu}>
807
<View style={styles.item}>
808
<Text>{item.title}</Text>
809
</View>
810
</TouchableOpacity>
811
);
812
}
813
814
// Haptic feedback with interactions
815
function HapticButton({onPress, children, feedbackType = 'impact'}) {
816
const handlePress = () => {
817
// Provide haptic feedback
818
if (Platform.OS === 'ios') {
819
// iOS haptic feedback would go here if available
820
Vibration.vibrate(50);
821
} else {
822
Vibration.vibrate(30);
823
}
824
825
onPress?.();
826
};
827
828
return (
829
<TouchableOpacity onPress={handlePress}>
830
{children}
831
</TouchableOpacity>
832
);
833
}
834
835
// Double tap detection
836
function DoubleTapView({onSingleTap, onDoubleTap, children}) {
837
const lastTap = useRef(null);
838
839
const handleTap = () => {
840
const now = Date.now();
841
const DOUBLE_PRESS_DELAY = 300;
842
843
if (lastTap.current && (now - lastTap.current) < DOUBLE_PRESS_DELAY) {
844
onDoubleTap?.();
845
} else {
846
setTimeout(() => {
847
if (lastTap.current && (Date.now() - lastTap.current) >= DOUBLE_PRESS_DELAY) {
848
onSingleTap?.();
849
}
850
}, DOUBLE_PRESS_DELAY);
851
}
852
853
lastTap.current = now;
854
};
855
856
return (
857
<TouchableWithoutFeedback onPress={handleTap}>
858
{children}
859
</TouchableWithoutFeedback>
860
);
861
}
862
```
863
864
This comprehensive user interaction documentation provides developers with all the tools needed to create engaging, accessible, and platform-appropriate user experiences in React Native applications.