0
# Platform APIs
1
2
React Native's platform APIs adapted for web environments, including device information, system services, web integrations, and cross-platform utilities.
3
4
## Platform
5
6
Provides platform detection and conditional code execution with web-specific implementations.
7
8
```javascript { .api }
9
const Platform: {
10
OS: 'web';
11
Version: string;
12
isTesting: boolean;
13
select: <T>(specifics: {web?: T, default?: T}) => T;
14
};
15
```
16
17
**Properties:**
18
- `OS` - Always `'web'` for React Native Web
19
- `Version` - Platform version (always `'0.0.0'` for web)
20
- `isTesting` - Whether running in test environment
21
22
**Methods:**
23
- `select(obj)` - Select platform-specific values
24
25
**Usage:**
26
```javascript
27
import { Platform } from "react-native-web";
28
29
// Check platform
30
if (Platform.OS === 'web') {
31
console.log('Running on web');
32
}
33
34
// Platform-specific values
35
const styles = StyleSheet.create({
36
container: {
37
flex: 1,
38
paddingTop: Platform.select({
39
web: 20,
40
default: 0
41
})
42
},
43
text: {
44
fontSize: Platform.select({
45
web: 16,
46
default: 14
47
})
48
}
49
});
50
51
// Platform-specific components
52
const HeaderComponent = Platform.select({
53
web: WebHeader,
54
default: DefaultHeader
55
});
56
57
// Conditional rendering
58
function App() {
59
return (
60
<View>
61
<Text>Hello React Native Web!</Text>
62
{Platform.OS === 'web' && (
63
<Text>This only shows on web</Text>
64
)}
65
</View>
66
);
67
}
68
69
// Environment detection
70
const isProduction = !Platform.isTesting && process.env.NODE_ENV === 'production';
71
```
72
73
## StatusBar
74
75
Component and API for controlling the status bar appearance and behavior. On web, this is a compatibility component that provides no-op implementations for mobile-specific status bar functionality.
76
77
```javascript { .api }
78
const StatusBar: React.ComponentType<StatusBarProps> & {
79
setBackgroundColor: (color: string, animated?: boolean) => void;
80
setBarStyle: (style: 'default' | 'light-content' | 'dark-content', animated?: boolean) => void;
81
setHidden: (hidden: boolean, animation?: 'none' | 'fade' | 'slide') => void;
82
setNetworkActivityIndicatorVisible: (visible: boolean) => void;
83
setTranslucent: (translucent: boolean) => void;
84
};
85
```
86
87
**Web Implementation:**
88
StatusBar on web is a compatibility layer that renders null and provides no-op static methods. This ensures cross-platform compatibility for apps that use StatusBar on mobile platforms.
89
90
### Component Usage
91
92
```javascript { .api }
93
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" hidden={false} />
94
```
95
96
### Static Methods
97
98
```javascript { .api }
99
// All static methods are no-ops on web but maintain compatibility
100
StatusBar.setBackgroundColor(color: string, animated?: boolean): void
101
StatusBar.setBarStyle(style: 'default' | 'light-content' | 'dark-content', animated?: boolean): void
102
StatusBar.setHidden(hidden: boolean, animation?: 'none' | 'fade' | 'slide'): void
103
StatusBar.setNetworkActivityIndicatorVisible(visible: boolean): void
104
StatusBar.setTranslucent(translucent: boolean): void
105
```
106
107
**Usage:**
108
```javascript
109
import React from "react";
110
import { StatusBar, View, Text } from "react-native-web";
111
112
function App() {
113
return (
114
<View style={{ flex: 1 }}>
115
{/* StatusBar component renders nothing on web but maintains compatibility */}
116
<StatusBar
117
barStyle="dark-content"
118
backgroundColor="#ffffff"
119
hidden={false}
120
/>
121
122
<Text>App content here</Text>
123
</View>
124
);
125
}
126
127
// Static methods are no-ops on web
128
function setStatusBarStyle() {
129
// These calls do nothing on web but don't throw errors
130
StatusBar.setBarStyle('light-content');
131
StatusBar.setBackgroundColor('#000000');
132
StatusBar.setHidden(true, 'fade');
133
}
134
```
135
136
**Cross-Platform Considerations:**
137
```javascript
138
import { Platform, StatusBar } from "react-native-web";
139
140
function ConfigureStatusBar() {
141
// Only configure status bar on mobile platforms
142
if (Platform.OS !== 'web') {
143
StatusBar.setBarStyle('light-content');
144
StatusBar.setBackgroundColor('#1a1a1a');
145
}
146
147
// Or use the component approach (safe on all platforms)
148
return (
149
<StatusBar
150
barStyle="light-content"
151
backgroundColor="#1a1a1a"
152
/>
153
);
154
}
155
```
156
157
## Dimensions
158
159
Utilities for accessing screen and window dimensions with responsive design support and automatic updates.
160
161
```javascript { .api }
162
const Dimensions: {
163
get: (dimension: 'window' | 'screen') => DisplayMetrics;
164
addEventListener: (type: 'change', handler: (dimensions: DimensionsValue) => void) => EventSubscription;
165
removeEventListener: (type: 'change', handler: Function) => void;
166
set: (initialDimensions?: DimensionsValue) => void;
167
};
168
```
169
170
**DisplayMetrics:**
171
```javascript
172
{
173
width: number; // Width in CSS pixels
174
height: number; // Height in CSS pixels
175
scale: number; // Device pixel ratio
176
fontScale: number; // Font scale factor (always 1 on web)
177
}
178
```
179
180
**Dimensions:**
181
- `window` - Viewport dimensions (changes with browser resize)
182
- `screen` - Screen dimensions (full screen size)
183
184
**Usage:**
185
```javascript
186
import { Dimensions } from "react-native-web";
187
188
// Get dimensions
189
const windowDimensions = Dimensions.get('window');
190
const screenDimensions = Dimensions.get('screen');
191
192
console.log('Window:', windowDimensions);
193
// { width: 1024, height: 768, scale: 2, fontScale: 1 }
194
195
console.log('Screen:', screenDimensions);
196
// { width: 1920, height: 1080, scale: 2, fontScale: 1 }
197
198
// Responsive design
199
function ResponsiveComponent() {
200
const [dimensions, setDimensions] = useState(Dimensions.get('window'));
201
202
useEffect(() => {
203
const subscription = Dimensions.addEventListener('change', ({ window }) => {
204
setDimensions(window);
205
});
206
207
return () => subscription?.remove();
208
}, []);
209
210
const isTablet = dimensions.width >= 768;
211
const isDesktop = dimensions.width >= 1024;
212
213
return (
214
<View style={[
215
styles.container,
216
isTablet && styles.tablet,
217
isDesktop && styles.desktop
218
]}>
219
<Text>Current width: {dimensions.width}px</Text>
220
<Text>Device type: {isDesktop ? 'Desktop' : isTablet ? 'Tablet' : 'Mobile'}</Text>
221
</View>
222
);
223
}
224
225
// Breakpoint utilities
226
const breakpoints = {
227
mobile: 0,
228
tablet: 768,
229
desktop: 1024,
230
wide: 1200
231
};
232
233
function getBreakpoint(width) {
234
if (width >= breakpoints.wide) return 'wide';
235
if (width >= breakpoints.desktop) return 'desktop';
236
if (width >= breakpoints.tablet) return 'tablet';
237
return 'mobile';
238
}
239
240
// Layout calculations
241
function calculateLayout() {
242
const { width, height } = Dimensions.get('window');
243
const isLandscape = width > height;
244
const aspectRatio = width / height;
245
246
return {
247
isLandscape,
248
aspectRatio,
249
gridColumns: Math.floor(width / 300), // 300px per column
250
itemWidth: (width - 40) / 2 // 20px margin, 2 columns
251
};
252
}
253
```
254
255
## AppState
256
257
Application state management for detecting when the app becomes active, inactive, or goes to background using the Page Visibility API.
258
259
```javascript { .api }
260
const AppState: {
261
currentState: 'active' | 'background';
262
isAvailable: boolean;
263
addEventListener: (type: 'change' | 'memoryWarning', handler: (state: string) => void) => EventSubscription;
264
};
265
```
266
267
**States:**
268
- `active` - App is in foreground and visible
269
- `background` - App is in background (page is hidden)
270
271
**Usage:**
272
```javascript
273
import { AppState } from "react-native-web";
274
275
// Get current state
276
console.log('Current state:', AppState.currentState);
277
278
// Listen for state changes
279
function AppStateExample() {
280
const [appState, setAppState] = useState(AppState.currentState);
281
282
useEffect(() => {
283
const handleAppStateChange = (nextAppState) => {
284
console.log('App state changed to:', nextAppState);
285
setAppState(nextAppState);
286
};
287
288
const subscription = AppState.addEventListener('change', handleAppStateChange);
289
290
return () => {
291
subscription?.remove();
292
};
293
}, []);
294
295
return (
296
<View>
297
<Text>App State: {appState}</Text>
298
{appState === 'active' && (
299
<Text>App is active and visible</Text>
300
)}
301
{appState === 'background' && (
302
<Text>App is in background</Text>
303
)}
304
</View>
305
);
306
}
307
308
// Pause video when app goes to background
309
function VideoPlayer() {
310
const [isPaused, setIsPaused] = useState(false);
311
312
useEffect(() => {
313
const handleAppStateChange = (nextAppState) => {
314
if (nextAppState === 'background') {
315
setIsPaused(true);
316
} else if (nextAppState === 'active') {
317
// Optionally resume when app becomes active
318
// setIsPaused(false);
319
}
320
};
321
322
AppState.addEventListener('change', handleAppStateChange);
323
}, []);
324
325
return (
326
<Video
327
source={{ uri: 'video.mp4' }}
328
paused={isPaused}
329
onPress={() => setIsPaused(!isPaused)}
330
/>
331
);
332
}
333
334
// Auto-save when app goes to background
335
function AutoSaveForm() {
336
const [formData, setFormData] = useState({});
337
338
useEffect(() => {
339
const handleAppStateChange = (nextAppState) => {
340
if (nextAppState === 'background') {
341
// Save form data
342
localStorage.setItem('formData', JSON.stringify(formData));
343
}
344
};
345
346
AppState.addEventListener('change', handleAppStateChange);
347
}, [formData]);
348
349
// ... rest of component
350
}
351
```
352
353
## PixelRatio
354
355
Utilities for working with device pixel density and converting between logical and physical pixels.
356
357
```javascript { .api }
358
const PixelRatio: {
359
get: () => number;
360
getFontScale: () => number;
361
getPixelSizeForLayoutSize: (layoutSize: number) => number;
362
roundToNearestPixel: (layoutSize: number) => number;
363
};
364
```
365
366
**Methods:**
367
- `get()` - Returns device pixel ratio (e.g., 2 for Retina displays)
368
- `getFontScale()` - Returns font scale multiplier (always 1 on web)
369
- `getPixelSizeForLayoutSize(dp)` - Convert density-independent pixels to actual pixels
370
- `roundToNearestPixel(dp)` - Round to nearest pixel boundary
371
372
**Usage:**
373
```javascript
374
import { PixelRatio } from "react-native-web";
375
376
// Get pixel ratio
377
const pixelRatio = PixelRatio.get();
378
console.log('Device pixel ratio:', pixelRatio); // 2 on Retina displays
379
380
// Convert logical pixels to physical pixels
381
const logicalSize = 100;
382
const physicalSize = PixelRatio.getPixelSizeForLayoutSize(logicalSize);
383
console.log(`${logicalSize}dp = ${physicalSize}px`);
384
385
// Round to pixel boundaries for crisp rendering
386
const crispSize = PixelRatio.roundToNearestPixel(100.7);
387
console.log('Crisp size:', crispSize); // 100.5 on 2x display
388
389
// High-DPI image selection
390
function HighDPIImage({ src, alt, width, height }) {
391
const pixelRatio = PixelRatio.get();
392
const imageSrc = pixelRatio > 1 ? `${src}@2x.png` : `${src}.png`;
393
394
return (
395
<img
396
src={imageSrc}
397
alt={alt}
398
width={width}
399
height={height}
400
style={{
401
width: PixelRatio.roundToNearestPixel(width),
402
height: PixelRatio.roundToNearestPixel(height)
403
}}
404
/>
405
);
406
}
407
408
// Pixel-perfect borders
409
const styles = StyleSheet.create({
410
separator: {
411
height: 1 / PixelRatio.get(), // Always 1 physical pixel
412
backgroundColor: '#ccc'
413
},
414
crispContainer: {
415
width: PixelRatio.roundToNearestPixel(200.3),
416
height: PixelRatio.roundToNearestPixel(150.7),
417
borderWidth: 1 / PixelRatio.get()
418
}
419
});
420
```
421
422
## Keyboard
423
424
Keyboard utilities with limited web functionality. Mainly provides compatibility with React Native code.
425
426
```javascript { .api }
427
const Keyboard: {
428
dismiss: () => void;
429
addListener: (eventType: string, callback: Function) => EventSubscription;
430
removeListener: (eventType: string, callback: Function) => void;
431
removeAllListeners: (eventType: string) => void;
432
isVisible: () => boolean;
433
};
434
```
435
436
**Usage:**
437
```javascript
438
import { Keyboard } from "react-native-web";
439
440
// Dismiss keyboard (blurs active input)
441
function DismissKeyboard() {
442
const handleSubmit = () => {
443
// Process form
444
Keyboard.dismiss();
445
};
446
447
return (
448
<View>
449
<TextInput placeholder="Type here..." />
450
<Button title="Submit" onPress={handleSubmit} />
451
</View>
452
);
453
}
454
455
// Note: Keyboard event listeners are no-ops on web
456
// Use standard DOM events or Visual Viewport API for keyboard detection
457
function WebKeyboardDetection() {
458
const [keyboardVisible, setKeyboardVisible] = useState(false);
459
460
useEffect(() => {
461
function handleResize() {
462
// Simple heuristic: significant height reduction might indicate keyboard
463
const currentHeight = window.innerHeight;
464
const isKeyboardVisible = currentHeight < window.screen.height * 0.8;
465
setKeyboardVisible(isKeyboardVisible);
466
}
467
468
// Better approach: Use Visual Viewport API if available
469
if (window.visualViewport) {
470
window.visualViewport.addEventListener('resize', handleResize);
471
return () => window.visualViewport.removeEventListener('resize', handleResize);
472
} else {
473
window.addEventListener('resize', handleResize);
474
return () => window.removeEventListener('resize', handleResize);
475
}
476
}, []);
477
478
return (
479
<View style={{ paddingBottom: keyboardVisible ? 20 : 0 }}>
480
<Text>Keyboard is {keyboardVisible ? 'visible' : 'hidden'}</Text>
481
</View>
482
);
483
}
484
```
485
486
## BackHandler
487
488
Android back button handler. On web, this is a no-op for React Native compatibility.
489
490
```javascript { .api }
491
const BackHandler: {
492
addEventListener: () => EventSubscription;
493
removeEventListener: () => void;
494
exitApp: () => void;
495
};
496
```
497
498
**Usage:**
499
```javascript
500
import { BackHandler, Platform } from "react-native-web";
501
502
// Cross-platform back handling
503
function NavigationHandler() {
504
useEffect(() => {
505
if (Platform.OS !== 'web') {
506
const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
507
// Handle back button on Android
508
return true; // Prevent default behavior
509
});
510
511
return () => backHandler.remove();
512
}
513
}, []);
514
515
// For web, use browser history or custom navigation
516
return <YourComponent />;
517
}
518
519
// Web-specific back button handling
520
function WebBackButton() {
521
const navigate = useNavigate(); // React Router or similar
522
523
const handleBack = () => {
524
if (Platform.OS === 'web') {
525
// Use browser history
526
window.history.back();
527
// Or programmatic navigation
528
// navigate(-1);
529
} else {
530
// Let BackHandler handle it on mobile
531
}
532
};
533
534
return (
535
<TouchableOpacity onPress={handleBack}>
536
<Text>← Back</Text>
537
</TouchableOpacity>
538
);
539
}
540
```
541
542
## DeviceEventEmitter
543
544
Global event emitter for cross-component communication and device events.
545
546
```javascript { .api }
547
const DeviceEventEmitter: {
548
addListener: (eventType: string, callback: Function) => EventSubscription;
549
removeListener: (eventType: string, callback: Function) => void;
550
emit: (eventType: string, data?: any) => void;
551
removeAllListeners: (eventType?: string) => void;
552
};
553
```
554
555
**Usage:**
556
```javascript
557
import { DeviceEventEmitter } from "react-native-web";
558
559
// Global event communication
560
function EventEmitterExample() {
561
useEffect(() => {
562
const subscription = DeviceEventEmitter.addListener('customEvent', (data) => {
563
console.log('Received event:', data);
564
});
565
566
return () => subscription.remove();
567
}, []);
568
569
const emitEvent = () => {
570
DeviceEventEmitter.emit('customEvent', {
571
message: 'Hello from emitter!',
572
timestamp: Date.now()
573
});
574
};
575
576
return (
577
<Button title="Emit Event" onPress={emitEvent} />
578
);
579
}
580
581
// Cross-component notifications
582
class NotificationService {
583
static notify(type, message) {
584
DeviceEventEmitter.emit('notification', { type, message });
585
}
586
587
static success(message) {
588
this.notify('success', message);
589
}
590
591
static error(message) {
592
this.notify('error', message);
593
}
594
}
595
596
function NotificationListener() {
597
const [notification, setNotification] = useState(null);
598
599
useEffect(() => {
600
const subscription = DeviceEventEmitter.addListener('notification', (data) => {
601
setNotification(data);
602
setTimeout(() => setNotification(null), 3000);
603
});
604
605
return () => subscription.remove();
606
}, []);
607
608
if (!notification) return null;
609
610
return (
611
<View style={[
612
styles.notification,
613
{ backgroundColor: notification.type === 'error' ? 'red' : 'green' }
614
]}>
615
<Text style={styles.notificationText}>{notification.message}</Text>
616
</View>
617
);
618
}
619
```
620
621
## Types
622
623
```javascript { .api }
624
interface DisplayMetrics {
625
width: number;
626
height: number;
627
scale: number;
628
fontScale: number;
629
}
630
631
interface DimensionsValue {
632
window: DisplayMetrics;
633
screen: DisplayMetrics;
634
}
635
636
interface EventSubscription {
637
remove: () => void;
638
}
639
640
interface PlatformStatic {
641
OS: 'web';
642
Version: string;
643
isTesting: boolean;
644
select: <T>(specifics: {web?: T, default?: T}) => T;
645
}
646
647
interface DimensionsStatic {
648
get: (dimension: 'window' | 'screen') => DisplayMetrics;
649
addEventListener: (type: 'change', handler: (dimensions: DimensionsValue) => void) => EventSubscription;
650
removeEventListener: (type: 'change', handler: Function) => void;
651
set: (initialDimensions?: DimensionsValue) => void;
652
}
653
654
interface AppStateStatic {
655
currentState: 'active' | 'background';
656
isAvailable: boolean;
657
addEventListener: (type: 'change' | 'memoryWarning', handler: (state: string) => void) => EventSubscription | undefined;
658
}
659
660
interface PixelRatioStatic {
661
get: () => number;
662
getFontScale: () => number;
663
getPixelSizeForLayoutSize: (layoutSize: number) => number;
664
roundToNearestPixel: (layoutSize: number) => number;
665
}
666
667
interface KeyboardStatic {
668
dismiss: () => void;
669
addListener: (eventType: string, callback: Function) => EventSubscription;
670
removeListener: (eventType: string, callback: Function) => void;
671
removeAllListeners: (eventType?: string) => void;
672
isVisible: () => boolean;
673
}
674
675
interface BackHandlerStatic {
676
addEventListener: (type: 'hardwareBackPress', handler: () => boolean) => EventSubscription;
677
removeEventListener: (type: 'hardwareBackPress', handler: Function) => void;
678
exitApp: () => void;
679
}
680
681
interface StatusBarProps {
682
animated?: boolean;
683
backgroundColor?: string;
684
barStyle?: 'default' | 'light-content' | 'dark-content';
685
hidden?: boolean;
686
networkActivityIndicatorVisible?: boolean;
687
showHideTransition?: 'fade' | 'slide';
688
translucent?: boolean;
689
}
690
```