NexusCS

React Native

React
React Native lets you build native mobile apps using React. This guide covers React Native 0.83+ with Expo and React Navigation 7.
featured

Getting started

Installation

# Expo (recommended)
npx create-expo-app@latest MyApp
cd MyApp && npx expo start
# Bare React Native CLI
npx react-native@latest init MyApp
cd MyApp
npx react-native run-ios
npx react-native run-android

Quick Example

import { View, Text, StyleSheet } from "react-native";

export default function App() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Hello World</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: "center" },
  title: { fontSize: 24, fontWeight: "bold" },
});

Key Differences from Web

  • No <div>, <span> — use <View>, <Text>
  • All text MUST be inside <Text> component
  • No CSS files — styles are JS objects
  • No CSS cascade or selectors
  • Default flexDirection: 'column' (web: row)
  • Units are density-independent pixels

Components

View

import { View, Text } from "react-native";

<View style={{ flex: 1, padding: 20 }}>
  <Text>Hello World</Text>
</View>;

The fundamental building block. Like <div> in HTML.

Text

import { Text } from "react-native";

<Text style={{ fontSize: 16, fontWeight: "bold" }}>Hello World</Text>;

All text must be inside <Text>. Nested text inherits styles:

<Text style={{ fontWeight: "bold" }}>
  Bold <Text style={{ color: "red" }}>and red</Text>
</Text>

Image

import { Image } from 'react-native';

// Remote image (must specify dimensions)
<Image
  source={{ uri: 'https://example.com/img.jpg' }}
  style={{ width: 200, height: 200 }}
/>

// Local image
<Image source={require('./assets/logo.png')} />

TextInput

import { TextInput } from "react-native";

<TextInput
  value={text}
  onChangeText={setText}
  placeholder="Enter text"
  keyboardType="email-address"
  secureTextEntry={false}
  multiline={false}
  autoCapitalize="none"
/>;
Prop Values
keyboardType default numeric email-address phone-pad
autoCapitalize none sentences words characters

Pressable

import { Pressable, Text } from "react-native";

<Pressable
  onPress={() => console.log("Pressed!")}
  onLongPress={() => console.log("Long pressed!")}
  style={({ pressed }) => [{ opacity: pressed ? 0.5 : 1, padding: 10 }]}
>
  <Text>Press me</Text>
</Pressable>;

Modern replacement for TouchableOpacity.

FlatList

import { FlatList } from "react-native";

<FlatList
  data={[{ id: "1", title: "Item 1" }]}
  renderItem={({ item }) => <Text>{item.title}</Text>}
  keyExtractor={(item) => item.id}
  onEndReached={loadMore}
  refreshing={isRefreshing}
  onRefresh={handleRefresh}
  numColumns={2}
  horizontal={false}
/>;

Use for long lists. Renders only visible items.

FlatList Performance

const renderItem = useCallback(({ item }) => <ItemComponent item={item} />, []);

<FlatList
  data={data}
  renderItem={renderItem}
  keyExtractor={(item) => item.id}
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>;

Always memoize renderItem and use getItemLayout if items have fixed height.

SectionList

import { SectionList } from "react-native";

<SectionList
  sections={[
    { title: "A", data: ["Alice", "Aaron"] },
    { title: "B", data: ["Bob", "Bill"] },
  ]}
  renderItem={({ item }) => <Text>{item}</Text>}
  renderSectionHeader={({ section }) => (
    <Text style={{ fontWeight: "bold" }}>{section.title}</Text>
  )}
  keyExtractor={(item, index) => item + index}
/>;

ScrollView

import { ScrollView } from "react-native";

<ScrollView
  horizontal={false}
  showsVerticalScrollIndicator={false}
  contentContainerStyle={{ padding: 20 }}
>
  <Text>Scrollable content</Text>
</ScrollView>;

⚠️ Use FlatList for long lists. ScrollView renders all children at once.

Modal

import { Modal, View, Text, Button } from "react-native";

<Modal
  animationType="slide"
  transparent={true}
  visible={modalVisible}
  onRequestClose={() => setModalVisible(false)}
>
  <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
    <Text>Modal content</Text>
    <Button title="Close" onPress={() => setModalVisible(false)} />
  </View>
</Modal>;
animationType Effect
none No animation
slide Slide from bottom
fade Fade in/out

Other Components

Component Purpose
SafeAreaView Renders within safe area (notch/status bar)
KeyboardAvoidingView Adjusts for keyboard (behavior: padding on iOS, height on Android)
ActivityIndicator Loading spinner (size="large" color="#0000ff")
StatusBar Configure status bar (barStyle="dark-content")
Switch Toggle switch (value={isOn} onValueChange={setIsOn})

Styling

StyleSheet.create

import { StyleSheet, View, Text } from "react-native";

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: "bold",
    color: "#333",
  },
});

<View style={styles.container}>
  <Text style={styles.title}>Hello</Text>
</View>;

StyleSheet.create validates styles and provides performance optimizations.

Combining Styles

// Array (last wins on conflict)
<View style={[styles.base, styles.override]} />

// Conditional
<View style={[styles.base, isActive && styles.active]} />

// Inline merge
<View style={[styles.base, { marginTop: 10 }]} />

Flexbox Layout

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: "column", // ⚠️ Default 'column' (web is 'row')
    justifyContent: "center",
    alignItems: "center",
    gap: 10,
  },
});
Property Values
justifyContent flex-start flex-end center space-between space-around space-evenly
alignItems flex-start flex-end center stretch baseline

CSS vs React Native

CSS React Native
display: flex Default (always flexbox)
flexDirection: row Default is column ⚠️
flexShrink: 1 Default is 0 ⚠️
flex: 1 1 0 Only single number: flex: 1
background-color backgroundColor (camelCase)
border-radius borderRadius (no shorthand)
CSS classes/cascade No cascade, no selectors
px, rem, em Unitless numbers (density-independent pixels)

Common Style Properties

{
  // Layout
  width: 100,
  height: 100,
  margin: 10,
  marginTop: 5,
  padding: 20,
  paddingHorizontal: 10,
  position: 'absolute',
  top: 0,
  left: 0,

  // Borders
  borderWidth: 1,
  borderColor: '#ccc',
  borderRadius: 8,

  // Shadows (iOS)
  shadowColor: '#000',
  shadowOffset: { width: 0, height: 2 },
  shadowOpacity: 0.25,
  shadowRadius: 4,

  // Shadows (Android)
  elevation: 5,

  // Text
  fontSize: 16,
  fontWeight: 'bold',
  textAlign: 'center',
  color: '#333',
}

APIs

Platform

import { Platform } from "react-native";

Platform.OS; // 'ios' | 'android' | 'web'
Platform.Version; // Android API level (number) | iOS version (string)

// Platform-specific values
Platform.select({
  ios: { marginTop: 20 },
  android: { marginTop: 25 },
  default: { marginTop: 0 },
});

Platform-specific Files

Button.ios.js       // iOS version
Button.android.js   // Android version

import Button from './Button';  // Auto-selects

React Native automatically imports the right file based on platform.

Dimensions

import { useWindowDimensions } from "react-native";

function MyComponent() {
  const { width, height, scale, fontScale } = useWindowDimensions();
  return <View style={{ width: width * 0.9 }} />;
}

Reactive to screen rotation. Prefer this over static Dimensions.get().

Animated API

import { Animated, Easing } from "react-native";

// Create animated value
const fadeAnim = useRef(new Animated.Value(0)).current;

// Timing animation
Animated.timing(fadeAnim, {
  toValue: 1,
  duration: 300,
  easing: Easing.ease,
  useNativeDriver: true,
}).start();

// Spring animation
Animated.spring(fadeAnim, {
  toValue: 1,
  friction: 7,
  tension: 40,
  useNativeDriver: true,
}).start();

⚠️ useNativeDriver: true only works with transform and opacity. Cannot animate width, height, backgroundColor.

Composing Animations

// Sequence (one after another)
Animated.sequence([anim1, anim2]).start();

// Parallel (at same time)
Animated.parallel([anim1, anim2]).start();

// Stagger (parallel with delays)
Animated.stagger(100, [anim1, anim2, anim3]).start();

// Loop
Animated.loop(anim).start();

Interpolation

const rotation = fadeAnim.interpolate({
  inputRange: [0, 1],
  outputRange: ["0deg", "360deg"],
});

<Animated.View style={{ transform: [{ rotate: rotation }] }} />;

Animatable components: Animated.View, Animated.Text, Animated.Image, Animated.ScrollView, Animated.FlatList

Linking

import { Linking } from "react-native";

Linking.openURL("https://example.com");
Linking.openURL("tel:+1234567890");
Linking.openURL("mailto:hello@example.com");

// Deep link handling
const url = await Linking.getInitialURL();

// Listen for URLs
const sub = Linking.addEventListener("url", ({ url }) => {
  console.log("Received:", url);
});
return () => sub.remove();

Alert

import { Alert } from "react-native";

Alert.alert("Title", "Message");

Alert.alert("Delete?", "Cannot be undone", [
  { text: "Cancel", style: "cancel" },
  { text: "Delete", onPress: handleDelete, style: "destructive" },
]);

AppState

import { AppState } from "react-native";

useEffect(() => {
  const sub = AppState.addEventListener("change", (state) => {
    // state: 'active' | 'background' | 'inactive' (iOS only)
    if (state === "active") console.log("Foreground");
  });
  return () => sub.remove();
}, []);

Keyboard

import { Keyboard } from "react-native";

Keyboard.dismiss();

useEffect(() => {
  const show = Keyboard.addListener("keyboardDidShow", (e) => {
    console.log("Height:", e.endCoordinates.height);
  });
  const hide = Keyboard.addListener("keyboardDidHide", () => {});
  return () => {
    show.remove();
    hide.remove();
  };
}, []);

Hooks

Hook Returns Purpose
useWindowDimensions() { width, height, scale, fontScale } Reactive window size
useColorScheme() 'light' 'dark' null Device color scheme

Navigation

Setup

npm install @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context
import { createStaticNavigation } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";

const RootStack = createNativeStackNavigator({
  screens: {
    Home: HomeScreen,
    Profile: {
      screen: ProfileScreen,
      options: { title: "Profile" },
    },
  },
});

const Navigation = createStaticNavigation(RootStack);

export default function App() {
  return <Navigation />;
}

Navigation Hooks

import { useNavigation, useRoute } from "@react-navigation/native";

function HomeScreen() {
  const navigation = useNavigation();
  return (
    <Button
      title="Go to Profile"
      onPress={() => navigation.navigate("Profile", { name: "Jane" })}
    />
  );
}

function ProfileScreen() {
  const route = useRoute();
  return <Text>{route.params.name}</Text>;
}

Navigator Types

Type Package Use Case
Stack @react-navigation/native-stack Card-style push/pop
Bottom Tabs @react-navigation/bottom-tabs Tab bar
Drawer @react-navigation/drawer Side menu
Material Top Tabs @react-navigation/material-top-tabs Swipeable tabs

Gotchas

Default flexDirection is 'column'

In React Native, flexDirection defaults to 'column'. On web, it defaults to 'row'.

// React Native (default)
<View style={{ flexDirection: 'column' }}>  {/* vertical */}

// Web (default)
<div style={{ display: 'flex' }}>  {/* horizontal */}

Default flexShrink is 0

React Native uses flexShrink: 0 by default. Web uses flexShrink: 1.

// React Native: items won't shrink below their natural size
<View style={{ flex: 1 }}>  {/* flex: 1 0 0% */}

// Web: items will shrink
<div style={{ flex: 1 }}>  {/* flex: 1 1 0% */}

All text must be in

You cannot render text outside of a <Text> component.

// ❌ Error
<View>Hello</View>

// ✅ Correct
<View><Text>Hello</Text></View>

No CSS cascade

Styles don't cascade. Each component needs explicit styles.

// ❌ Won't work (no cascade)
<View style={{ color: 'red' }}>
  <Text>Not red</Text>
</View>

// ✅ Correct
<View>
  <Text style={{ color: 'red' }}>Red text</Text>
</View>

Exception: nested <Text> inherits styles from parent <Text>.

Units are density-independent pixels

All numbers are unitless and represent density-independent pixels.

// ❌ Don't use px, rem, em
<View style={{ width: '100px' }} />

// ✅ Use numbers
<View style={{ width: 100 }} />

useNativeDriver limitations

useNativeDriver: true only works with transform and opacity.

// ✅ Works with useNativeDriver
Animated.timing(anim, {
  toValue: 1,
  useNativeDriver: true,
}).start();
<Animated.View style={{ opacity: anim }} />

// ❌ Doesn't work with useNativeDriver
<Animated.View style={{ backgroundColor: anim }} />

For non-transform/opacity properties, set useNativeDriver: false.

Also see