Skip to main content

Core Concepts & Architecture

Understanding React Native's architecture and core concepts is crucial for building efficient mobile applications. This section covers the fundamental concepts that differ from web development.

React Native Architecture

Bridge Architecture (Legacy)

The traditional React Native architecture uses a bridge to communicate between JavaScript and native code:

┌─────────────────┐    ┌─────────┐    ┌─────────────────┐
│ JavaScript │◄──►│ Bridge │◄──►│ Native │
│ Thread │ │ │ │ Thread │
│ │ │ │ │ │
│ • Business Logic│ │ Async │ │ • UI Rendering │
│ • State Mgmt │ │ JSON │ │ • Platform APIs │
│ • Component Tree│ │ Messages│ │ • Native Modules│
└─────────────────┘ └─────────┘ └─────────────────┘

Characteristics:

  • Asynchronous communication via JSON messages
  • Serialization overhead for data transfer
  • Potential performance bottlenecks with frequent bridge crossing

New Architecture (Fabric + TurboModules)

The modern architecture introduces direct communication and improved performance:

┌─────────────────┐              ┌─────────────────┐
│ JavaScript │◄────JSI────►│ Native │
│ Thread │ │ Thread │
│ │ │ │
│ • React │ │ • Fabric │
│ • TurboModules │ │ • Native Modules│
│ • Direct Calls │ │ • UI Operations │
└─────────────────┘ └─────────────────┘

Key Improvements:

  • JSI (JavaScript Interface): Direct JavaScript-to-native communication
  • Fabric: New rendering system with synchronous UI updates
  • TurboModules: Lazy-loaded native modules with better performance
  • Codegen: Type-safe interfaces between JS and native code

Threading Model

React Native operates on multiple threads to maintain smooth performance:

Main Thread (UI Thread)

  • Purpose: Handles UI rendering and user interactions
  • Platform: Native iOS/Android UI operations
  • Performance: Must stay responsive (60 FPS target)
// Operations that affect UI thread
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
})

JavaScript Thread

  • Purpose: Executes your React code and business logic
  • Operations: State updates, API calls, calculations
  • Communication: Sends UI updates to main thread
// JavaScript thread operations
const [data, setData] = useState([])

useEffect(() => {
// API call runs on JS thread
fetchData().then(setData)
}, [])

Background Threads

  • Purpose: Heavy operations like image processing, file I/O
  • Examples: Network requests, database operations, intensive calculations
// Background operations
import { Image } from "expo-image"

// Image loading happens on background thread
;<Image source={{ uri: "https://example.com/large-image.jpg" }} contentFit="cover" />

Component Lifecycle in Mobile Context

React Native components follow React lifecycle patterns with mobile-specific considerations:

App State Lifecycle

import { AppState } from "react-native"

export function useAppState() {
const [appState, setAppState] = useState(AppState.currentState)

useEffect(() => {
const subscription = AppState.addEventListener("change", (nextAppState) => {
if (appState.match(/inactive|background/) && nextAppState === "active") {
console.log("App has come to the foreground!")
// Refresh data, restart timers, etc.
} else if (nextAppState.match(/inactive|background/)) {
console.log("App has gone to the background!")
// Save data, pause operations, etc.
}
setAppState(nextAppState)
})

return () => subscription?.remove()
}, [appState])

return appState
}

Screen Focus Lifecycle

import { useFocusEffect } from "@react-navigation/native"

export function ProfileScreen() {
useFocusEffect(
useCallback(() => {
// Screen is focused
console.log("Screen focused")

// Cleanup when screen loses focus
return () => {
console.log("Screen unfocused")
}
}, [])
)

return <View>{/* Screen content */}</View>
}

Memory Management

Mobile devices have limited memory, making efficient memory management crucial:

Component Cleanup

function DataScreen() {
const [data, setData] = useState([])
const intervalRef = useRef<NodeJS.Timeout>()

useEffect(() => {
// Set up interval
intervalRef.current = setInterval(() => {
fetchLatestData().then(setData)
}, 5000)

// Critical: cleanup on unmount
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
}
}, [])

return <DataList data={data} />
}

Event Listener Cleanup

function LocationScreen() {
useEffect(() => {
const subscription = Location.watchPositionAsync({ accuracy: Location.Accuracy.High }, (location) => {
setCurrentLocation(location)
})

return () => {
subscription.then((sub) => sub.remove())
}
}, [])
}

Platform Differences

React Native abstracts many platform differences, but understanding them helps with optimization:

iOS Specifics

import { Platform } from "react-native"

const styles = StyleSheet.create({
container: {
flex: 1,
// iOS safe area handling
paddingTop: Platform.OS === "ios" ? 44 : 0,
},
shadow: Platform.select({
ios: {
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 3.84,
},
android: {
elevation: 5,
},
}),
})

Android Specifics

import { BackHandler } from "react-native"

function useBackHandler(handler: () => boolean) {
useEffect(() => {
if (Platform.OS === "android") {
const subscription = BackHandler.addEventListener("hardwareBackPress", handler)
return () => subscription.remove()
}
}, [handler])
}

Performance Considerations

Avoiding Bridge Bottlenecks

// ❌ Frequent bridge crossings
setInterval(() => {
// This crosses the bridge every 16ms!
Animated.timing(animatedValue, {
toValue: 1,
duration: 16,
useNativeDriver: false, // Uses bridge
}).start()
}, 16)

// ✅ Native animations
Animated.timing(animatedValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true, // Runs on native thread
}).start()

Optimizing List Performance

// ✅ Optimized list rendering
function OptimizedList({ data }: { data: Item[] }) {
const renderItem = useCallback(({ item }: { item: Item }) => <ListItem item={item} />, [])

const keyExtractor = useCallback((item: Item) => item.id, [])

return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={10}
initialNumToRender={10}
getItemLayout={(data, index) => ({
length: 60,
offset: 60 * index,
index,
})}
/>
)
}

Error Boundaries for Mobile

Error boundaries are crucial for mobile apps to prevent crashes:

import { ErrorBoundary } from "react-error-boundary"

function ErrorFallback({ error, resetErrorBoundary }: any) {
return (
<View style={styles.errorContainer}>
<Text style={styles.errorTitle}>Something went wrong</Text>
<Text style={styles.errorMessage}>{error.message}</Text>
<Pressable style={styles.retryButton} onPress={resetErrorBoundary}>
<Text style={styles.retryText}>Try again</Text>
</Pressable>
</View>
)
}

function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, errorInfo) => {
// Log to crash reporting service
console.error("App Error:", error, errorInfo)
}}
>
<MainNavigator />
</ErrorBoundary>
)
}

Development vs Production Differences

Understanding the differences between development and production builds:

Development Mode

  • JavaScript bundle runs in debug mode
  • Hot reloading and live reload enabled
  • Source maps available for debugging
  • Performance is slower due to development overhead

Production Mode

  • Optimized JavaScript bundle
  • No debugging tools
  • Better performance and smaller bundle size
  • Crash reporting and analytics enabled
import { __DEV__ } from "react-native"

function setupApp() {
if (__DEV__) {
// Development-only code
import("./DevMenu").then(({ enableDevMenu }) => {
enableDevMenu()
})
} else {
// Production-only code
crashlytics().log("App started in production mode")
}
}

Next Steps

Now that you understand the core concepts, you can move on to: