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: elevation only shows on View backgrounds — transparent Views won't cast a shadow. Set backgroundColor on 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-reanimated
import 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>

Back Button (Android)

import { BackHandler } from 'react-native';

// Handle hardware back button
useEffect(() => {
  const sub = BackHandler.addEventListener('hardwareBackPress', () => {
    if (modalVisible) {
      closeModal();
      return true;   // true = handled (prevent default back behavior)
    }
    return false;    // false = let default behavior happen (exit / go back)
  });
  return () => sub.remove();
}, [modalVisible]);

// With React Navigation — intercept navigation back
useEffect(() => {
  return navigation.addListener('beforeRemove', (e) => {
    if (!hasUnsavedChanges) return;
    e.preventDefault();
    Alert.alert('Discard changes?', '', [
      { text: 'Keep editing', style: 'cancel', onPress: () => {} },
      { text: 'Discard', style: 'destructive', onPress: () => navigation.dispatch(e.data.action) },
    ]);
  });
}, [navigation, hasUnsavedChanges]);

Text Input Differences

BehavioriOSAndroid
multiline paddingHas default top paddingAligns top
Cursor colortintColorcursorColor prop
UnderlineNoneShows colored underline by default
textContentTypeFull autofill supportUse autoComplete
Font renderingSubpixel smoothingNo 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-svg
import 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

PermissioniOS Info.plist keyAndroid uses-permission
CameraNSCameraUsageDescriptionCAMERA
Photo libraryNSPhotoLibraryUsageDescriptionREAD_MEDIA_IMAGES (API 33+)
Location (foreground)NSLocationWhenInUseUsageDescriptionACCESS_FINE_LOCATION
Location (background)NSLocationAlwaysUsageDescriptionACCESS_BACKGROUND_LOCATION
MicrophoneNSMicrophoneUsageDescriptionRECORD_AUDIO
ContactsNSContactsUsageDescriptionREAD_CONTACTS
NotificationsRequest via APIAutomatic (API < 33), request (API 33+)

Debugging Tools by Platform

TooliOSAndroidNotes
React DevToolsYesYesnpx react-devtools
React Native DevToolsYesYesPress j in Metro (RN 0.76+)
Safari Web InspectorYesNoWKWebView debugging
Chrome DevToolsYesYesadb reverse tcp:8081 tcp:8081
Android Studio ProfilerNoYesMemory, CPU, GPU
Xcode InstrumentsYesNoAllocation, Time Profiler
adb logcatNoYesNative 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