React Native Cheatsheet
Platform Differences
Use this React Native reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Platform Detection
import { Platform } from 'react-native'; // OS string Platform.OS // 'ios' | 'android' | 'windows' | 'macos' | 'web' // Boolean checks const isIOS = Platform.OS === 'ios'; const isAndroid = Platform.OS === 'android'; // Version Platform.Version // iOS: "17.2" (string) | Android: 33 (SDK integer) parseInt(Platform.Version as string, 10) // normalize iOS to number // Platform.select — returns the matching value const statusBarHeight = Platform.select({ ios: 44, android: 24, default: 0, }); // In styles const styles = StyleSheet.create({ header: { paddingTop: Platform.select({ ios: 44, android: 24, default: 0 }), ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.15, shadowRadius: 4, }, android: { elevation: 3 }, }), }, });
Platform-Specific Files
React Native's bundler auto-selects the right file:
Button.ios.tsx → imported on iOS
Button.android.tsx → imported on Android
Button.tsx → fallback for both / web// Works from the importing side — no .ios/.android in the import import Button from './Button'; // auto-picks .ios.tsx or .android.tsx
Safe Area Insets
iOS has notches, Dynamic Island, home indicator. Android has cutouts and gesture nav bars.
npm install react-native-safe-area-context
// Wrap app (above NavigationContainer) import { SafeAreaProvider } from 'react-native-safe-area-context'; <SafeAreaProvider> <App /> </SafeAreaProvider> // Hook — get raw inset values import { useSafeAreaInsets } from 'react-native-safe-area-context'; function Header() { const insets = useSafeAreaInsets(); // insets: { top, right, bottom, left } return <View style={{ paddingTop: insets.top + 12 }}>...</View>; } // Component — wraps children with padding import { SafeAreaView } from 'react-native-safe-area-context'; <SafeAreaView style={{ flex: 1 }} edges={['top', 'left', 'right']}> {/* edges: inset only these sides — omit 'bottom' if you have a tab bar */} </SafeAreaView>
Shadows
// iOS style={{ shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.15, shadowRadius: 8, }} // Android style={{ elevation: 4 }} // Cross-platform (no single shorthand — use Platform.select) const cardShadow = Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.12, shadowRadius: 6, }, android: { elevation: 3 }, }) ?? {};
Android shadow caveat:
elevationonly shows onViewbackgrounds — transparent Views won't cast a shadow. SetbackgroundColoron the shadowed View.
Status Bar
import { StatusBar, Platform } from 'react-native'; // iOS: overlaps content (transparent by default) // Android: occupies space by default <StatusBar barStyle="dark-content" // 'default' | 'light-content' | 'dark-content' backgroundColor="#fff" // Android only translucent={false} // Android: true = overlay content hidden={false} /> // Get status bar height (useful for layout) import { StatusBar as ExpoStatusBar } from 'expo-status-bar'; const STATUS_BAR_HEIGHT = Platform.OS === 'ios' ? 44 : StatusBar.currentHeight ?? 24;
Keyboard Behavior
import { KeyboardAvoidingView, Platform } from 'react-native'; // iOS: 'padding' or 'position' works best // Android: 'height' is most reliable — or set windowSoftInputMode in AndroidManifest.xml <KeyboardAvoidingView style={{ flex: 1 }} behavior={Platform.OS === 'ios' ? 'padding' : 'height'} keyboardVerticalOffset={Platform.select({ ios: 64, android: 0 })} > ... </KeyboardAvoidingView>
Android windowSoftInputMode (AndroidManifest.xml)
<activity android:windowSoftInputMode="adjustResize">adjustResize — resizes the layout (recommended for forms)
adjustPan — pans the layout up (default)
adjustNothing — no automatic behavior
Fonts
Font family name must match the font file name on Android; iOS uses the PostScript name, Android uses the filename without extension.
// React Native CLI — react-native.config.js module.exports = { assets: ['./assets/fonts/'] };
# Link the fonts into both native projects
npx react-native-asset// Expo — useFonts hook import { useFonts } from 'expo-font'; const [fontsLoaded] = useFonts({ 'Roboto-Regular': require('./assets/fonts/Roboto-Regular.ttf'), 'Roboto-Bold': require('./assets/fonts/Roboto-Bold.ttf'), }); // fontWeight on Android with custom fonts // Android ignores fontWeight if you've loaded a specific font file // Solution: load both Roboto-Regular.ttf and Roboto-Bold.ttf, use fontFamily to switch
Animations & Performance
// useNativeDriver: true — runs on UI thread (iOS CALayer / Android RenderThread) // Supported: opacity, transform // NOT supported: width, height, backgroundColor, top, left, flex... Animated.timing(value, { toValue: 1, duration: 300, useNativeDriver: true, // always use when possible }).start();
Reanimated (v3+) runs worklets on the UI thread and can animate anything — transforms, colors, layout. See the Animations and Gestures topic for the full reference.
npx expo install react-native-reanimated
# or: npm install react-native-reanimatedimport Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming } from 'react-native-reanimated'; const offset = useSharedValue(0); const animatedStyles = useAnimatedStyle(() => ({ transform: [{ translateX: offset.value }], })); offset.value = withSpring(100); <Animated.View style={[styles.box, animatedStyles]} />
Touch Handling Differences
// iOS: touchable area can extend outside bounds (hitSlop) // Android: touch ripple stays within View bounds (requires overflow: 'hidden' parent) // Android ripple effect import { Pressable } from 'react-native'; <Pressable android_ripple={{ color: 'rgba(0,0,0,0.1)', radius: 24, borderless: false }} style={styles.button} > <Text>Press me</Text> </Pressable> // borderless ripple (for icon buttons) <Pressable android_ripple={{ color: '#ccc', borderless: true }} style={{ padding: 8 }}> <Icon name="menu" size={24} /> </Pressable>
Text Input Differences
| Behavior | iOS | Android |
|---|---|---|
multiline padding | Has default top padding | Aligns top |
| Cursor color | tintColor | cursorColor prop |
| Underline | None | Shows colored underline by default |
textContentType | Full autofill support | Use autoComplete |
| Font rendering | Subpixel smoothing | No subpixel |
// Remove Android underline <TextInput underlineColorAndroid="transparent" style={{ paddingVertical: Platform.OS === 'ios' ? 12 : 8 }} />
Images
// Android: doesn't support GIFs out of the box // Enable in android/app/build.gradle: // implementation("com.facebook.fresco:animated-gif:2.6.0") // iOS: supports GIF natively
# SVG — neither platform supports natively; use react-native-svg
npm install react-native-svgimport Svg, { Circle, Rect, Path } from 'react-native-svg'; // Or load .svg as component import Logo from './assets/logo.svg'; <Logo width={100} height={100} fill="#000" />
Permissions Comparison
| Permission | iOS Info.plist key | Android uses-permission |
|---|---|---|
| Camera | NSCameraUsageDescription | CAMERA |
| Photo library | NSPhotoLibraryUsageDescription | READ_MEDIA_IMAGES (API 33+) |
| Location (foreground) | NSLocationWhenInUseUsageDescription | ACCESS_FINE_LOCATION |
| Location (background) | NSLocationAlwaysUsageDescription | ACCESS_BACKGROUND_LOCATION |
| Microphone | NSMicrophoneUsageDescription | RECORD_AUDIO |
| Contacts | NSContactsUsageDescription | READ_CONTACTS |
| Notifications | Request via API | Automatic (API < 33), request (API 33+) |
Debugging Tools by Platform
| Tool | iOS | Android | Notes |
|---|---|---|---|
| React DevTools | Yes | Yes | npx react-devtools |
| React Native DevTools | Yes | Yes | Press j in Metro (RN 0.76+) |
| Safari Web Inspector | Yes | No | WKWebView debugging |
| Chrome DevTools | Yes | Yes | adb reverse tcp:8081 tcp:8081 |
| Android Studio Profiler | No | Yes | Memory, CPU, GPU |
| Xcode Instruments | Yes | No | Allocation, Time Profiler |
adb logcat | No | Yes | Native Android logs |
# View Android logs filtered to RN adb logcat | grep -i reactnative # List iOS simulators xcrun simctl list devices # Capture Android screenshot adb exec-out screencap -p > screen.png