Advanced Topics
Explore advanced React Native concepts, patterns, and techniques for building sophisticated mobile applications.
Overview
This guide covers advanced React Native development topics that go beyond the fundamentals. These concepts are essential for building complex, production-ready applications with custom native functionality.
Table of Contents
- Custom Native Modules
- Bridge Communication
- Native UI Components
- Advanced Styling
- Custom Hooks & Patterns
- Memory Management
- Advanced Navigation
- Microinteractions
- Code Splitting
- Advanced Debugging
Custom Native Modules
Creating iOS Native Modules
// ios/YourApp/CustomModule.swift
import Foundation
import React
@objc(CustomModule)
class CustomModule: NSObject {
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
@objc
func getBatteryLevel(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
UIDevice.current.isBatteryMonitoringEnabled = true
let batteryLevel = UIDevice.current.batteryLevel
if batteryLevel < 0 {
reject("BATTERY_ERROR", "Unable to get battery level", nil)
} else {
resolve(batteryLevel * 100)
}
}
@objc
func showAlert(_ title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
if let rootViewController = UIApplication.shared.windows.first?.rootViewController {
rootViewController.present(alert, animated: true)
}
}
}
}
// ios/YourApp/CustomModule.m
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(CustomModule, NSObject)
RCT_EXTERN_METHOD(getBatteryLevel:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(showAlert:(NSString *)title message:(NSString *)message)
@end
Creating Android Native Modules
// android/app/src/main/java/com/yourapp/CustomModule.java
package com.yourapp;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.widget.Toast;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class CustomModule extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
CustomModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
@Override
public String getName() {
return "CustomModule";
}
@ReactMethod
public void getBatteryLevel(Promise promise) {
try {
Intent batteryIntent = reactContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
int level = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryLevel = level * 100 / (float) scale;
promise.resolve(batteryLevel);
} catch (Exception e) {
promise.reject("BATTERY_ERROR", "Unable to get battery level", e);
}
}
@ReactMethod
public void showToast(String message) {
Toast.makeText(getReactApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
}
// android/app/src/main/java/com/yourapp/CustomModulePackage.java
package com.yourapp;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CustomModulePackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new CustomModule(reactContext));
return modules;
}
}
TypeScript Integration
// src/modules/CustomModule.ts
import { NativeModules } from "react-native"
interface CustomModuleInterface {
getBatteryLevel(): Promise<number>
showAlert(title: string, message: string): void
showToast(message: string): void
}
const { CustomModule } = NativeModules
export default CustomModule as CustomModuleInterface
// Usage
import CustomModule from "@/modules/CustomModule"
const useBattery = () => {
const [batteryLevel, setBatteryLevel] = useState<number>(0)
const checkBatteryLevel = async () => {
try {
const level = await CustomModule.getBatteryLevel()
setBatteryLevel(level)
} catch (error) {
console.error("Failed to get battery level:", error)
}
}
return { batteryLevel, checkBatteryLevel }
}
Bridge Communication
Event Emitters
// Native module with event emitter
import { NativeEventEmitter, NativeModules } from "react-native"
const { LocationModule } = NativeModules
const locationEmitter = new NativeEventEmitter(LocationModule)
const useLocationUpdates = () => {
const [location, setLocation] = useState<Location | null>(null)
useEffect(() => {
const subscription = locationEmitter.addListener("LocationUpdate", (newLocation: Location) => {
setLocation(newLocation)
})
// Start location updates
LocationModule.startLocationUpdates()
return () => {
subscription.remove()
LocationModule.stopLocationUpdates()
}
}, [])
return location
}
Advanced Bridge Patterns
// Batch operations for performance
const BatchedNativeModule = {
operations: [] as Array<() => Promise<any>>,
addOperation<T>(operation: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.operations.push(async () => {
try {
const result = await operation()
resolve(result)
} catch (error) {
reject(error)
}
})
})
},
async executeBatch() {
const results = await Promise.allSettled(this.operations.map((op) => op()))
this.operations = []
return results
},
}
// Usage
const performBatchOperations = async () => {
BatchedNativeModule.addOperation(() => CustomModule.getBatteryLevel())
BatchedNativeModule.addOperation(() => CustomModule.getDeviceInfo())
const results = await BatchedNativeModule.executeBatch()
return results
}
Native UI Components
Creating Custom Views (iOS)
// ios/YourApp/CustomView.swift
import UIKit
import React
class CustomView: UIView {
@objc var onPress: RCTDirectEventBlock?
@objc var title: String = "" {
didSet {
titleLabel.text = title
}
}
private let titleLabel = UILabel()
private let button = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
addSubview(titleLabel)
addSubview(button)
button.setTitle("Press Me", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
// Auto Layout setup
titleLabel.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor),
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor),
button.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 8),
button.centerXAnchor.constraint(equalTo: centerXAnchor),
button.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
@objc private func buttonPressed() {
onPress?(["message": "Button was pressed!"])
}
}
// CustomViewManager.swift
import React
@objc(CustomViewManager)
class CustomViewManager: RCTViewManager {
override func view() -> UIView! {
return CustomView()
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
}
React Native Component Interface
// src/components/CustomView.tsx
import React from "react"
import { requireNativeComponent, ViewStyle, NativeSyntheticEvent } from "react-native"
interface CustomViewProps {
style?: ViewStyle
title: string
onPress?: (event: NativeSyntheticEvent<{ message: string }>) => void
}
const NativeCustomView = requireNativeComponent<CustomViewProps>("CustomView")
export const CustomView: React.FC<CustomViewProps> = (props) => {
return <NativeCustomView {...props} />
}
// Usage
const App = () => {
const handlePress = (event: NativeSyntheticEvent<{ message: string }>) => {
console.log("Native view pressed:", event.nativeEvent.message)
}
return <CustomView title="My Custom Component" onPress={handlePress} style={{ height: 100, margin: 20 }} />
}
Advanced Styling
Dynamic Styling System
// Advanced theming system
interface Theme {
colors: {
primary: string
secondary: string
background: string
surface: string
text: string
error: string
}
spacing: {
xs: number
sm: number
md: number
lg: number
xl: number
}
typography: {
heading: TextStyle
body: TextStyle
caption: TextStyle
}
}
const createTheme = (baseTheme: Partial<Theme>): Theme => {
return {
colors: {
primary: "#007AFF",
secondary: "#5856D6",
background: "#FFFFFF",
surface: "#F2F2F7",
text: "#000000",
error: "#FF3B30",
...baseTheme.colors,
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
...baseTheme.spacing,
},
typography: {
heading: {
fontSize: 24,
fontWeight: "600",
lineHeight: 32,
},
body: {
fontSize: 16,
fontWeight: "400",
lineHeight: 24,
},
caption: {
fontSize: 12,
fontWeight: "400",
lineHeight: 16,
},
...baseTheme.typography,
},
}
}
// Responsive styling
const useResponsiveStyle = () => {
const { width, height } = useWindowDimensions()
const isTablet = width >= 768
const isLandscape = width > height
const responsiveStyle = useMemo(() => {
return StyleSheet.create({
container: {
padding: isTablet ? 24 : 16,
flexDirection: isTablet && isLandscape ? "row" : "column",
},
content: {
flex: 1,
marginHorizontal: isTablet ? 16 : 0,
},
})
}, [isTablet, isLandscape])
return responsiveStyle
}
Animation-Based Styling
// Advanced animation patterns
const useAnimatedStyle = () => {
const scale = useSharedValue(1)
const opacity = useSharedValue(1)
const translateY = useSharedValue(0)
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ scale: scale.value }, { translateY: translateY.value }],
opacity: opacity.value,
}
})
const animate = {
press: () => {
scale.value = withSpring(0.95)
},
release: () => {
scale.value = withSpring(1)
},
slideIn: () => {
translateY.value = withTiming(0, { duration: 300 })
opacity.value = withTiming(1, { duration: 300 })
},
slideOut: () => {
translateY.value = withTiming(-50, { duration: 300 })
opacity.value = withTiming(0, { duration: 300 })
},
}
return { animatedStyle, animate }
}
Custom Hooks & Patterns
Advanced State Management Hooks
// Complex async state hook
interface AsyncState<T> {
data: T | null
loading: boolean
error: Error | null
refetch: () => Promise<void>
}
const useAsyncResource = <T>(fetchFn: () => Promise<T>, dependencies: any[] = []): AsyncState<T> => {
const [state, setState] = useState<AsyncState<T>>({
data: null,
loading: true,
error: null,
refetch: async () => {},
})
const fetchData = useCallback(async () => {
setState((prev) => ({ ...prev, loading: true, error: null }))
try {
const data = await fetchFn()
setState((prev) => ({ ...prev, data, loading: false }))
} catch (error) {
setState((prev) => ({
...prev,
error: error as Error,
loading: false,
}))
}
}, dependencies)
useEffect(() => {
fetchData()
}, [fetchData])
const refetch = useCallback(async () => {
await fetchData()
}, [fetchData])
return { ...state, refetch }
}
// Intersection observer hook for virtualization
const useIntersectionObserver = (threshold = 0.1): [React.RefObject<View>, boolean] => {
const ref = useRef<View>(null)
const [isIntersecting, setIsIntersecting] = useState(false)
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setIsIntersecting(entry.isIntersecting)
},
{ threshold }
)
if (ref.current) {
observer.observe(ref.current as any)
}
return () => observer.disconnect()
}, [threshold])
return [ref, isIntersecting]
}
Advanced Component Patterns
// Compound component pattern
interface TabsProps {
children: React.ReactNode
defaultValue?: string
onValueChange?: (value: string) => void
}
interface TabsContextType {
activeTab: string
setActiveTab: (tab: string) => void
}
const TabsContext = createContext<TabsContextType | undefined>(undefined)
const Tabs: React.FC<TabsProps> & {
List: typeof TabsList
Trigger: typeof TabsTrigger
Content: typeof TabsContent
} = ({ children, defaultValue = "", onValueChange }) => {
const [activeTab, setActiveTab] = useState(defaultValue)
const handleTabChange = useCallback(
(tab: string) => {
setActiveTab(tab)
onValueChange?.(tab)
},
[onValueChange]
)
return <TabsContext.Provider value={{ activeTab, setActiveTab: handleTabChange }}>{children}</TabsContext.Provider>
}
const TabsList: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return <View style={styles.tabsList}>{children}</View>
}
const TabsTrigger: React.FC<{
value: string
children: React.ReactNode
}> = ({ value, children }) => {
const context = useContext(TabsContext)
if (!context) throw new Error("TabsTrigger must be used within Tabs")
const { activeTab, setActiveTab } = context
const isActive = activeTab === value
return (
<TouchableOpacity
style={[styles.tabTrigger, isActive && styles.tabTriggerActive]}
onPress={() => setActiveTab(value)}
>
{children}
</TouchableOpacity>
)
}
const TabsContent: React.FC<{
value: string
children: React.ReactNode
}> = ({ value, children }) => {
const context = useContext(TabsContext)
if (!context) throw new Error("TabsContent must be used within Tabs")
const { activeTab } = context
if (activeTab !== value) return null
return <View style={styles.tabContent}>{children}</View>
}
Tabs.List = TabsList
Tabs.Trigger = TabsTrigger
Tabs.Content = TabsContent
Memory Management
Optimizing Re-renders
// Advanced memoization patterns
const ExpensiveComponent = React.memo<{
data: ComplexData[]
onItemPress: (id: string) => void
}>(
({ data, onItemPress }) => {
// Memoize expensive calculations
const processedData = useMemo(() => {
return data.map((item) => ({
...item,
processed: expensiveProcessing(item),
}))
}, [data])
// Stable callback references
const handleItemPress = useCallback(
(id: string) => {
onItemPress(id)
},
[onItemPress]
)
return (
<FlatList
data={processedData}
renderItem={({ item }) => <MemoizedItem item={item} onPress={handleItemPress} />}
keyExtractor={(item) => item.id}
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
windowSize={10}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
/>
)
},
(prevProps, nextProps) => {
// Custom comparison for complex props
return (
JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data) &&
prevProps.onItemPress === nextProps.onItemPress
)
}
)
// Memory leak prevention
const useMemoryEfficientEffect = (effect: () => (() => void) | void, deps: any[]) => {
const mountedRef = useRef(true)
useEffect(() => {
let cleanup: (() => void) | void
if (mountedRef.current) {
cleanup = effect()
}
return () => {
if (typeof cleanup === "function") {
cleanup()
}
}
}, deps)
useEffect(() => {
return () => {
mountedRef.current = false
}
}, [])
}
Advanced Navigation
Complex Navigation Patterns
// Advanced navigation with type safety
type RootStackParamList = {
Home: undefined
Profile: { userId: string }
Settings: { section?: string }
Modal: { content: string }
}
type TabParamList = {
HomeTab: NavigatorScreenParams<RootStackParamList>
SearchTab: undefined
NotificationsTab: { badge?: number }
}
// Custom navigation hook with state persistence
const useNavigationState = () => {
const navigation = useNavigation()
const [navigationHistory, setNavigationHistory] = useState<string[]>([])
const navigateWithHistory = useCallback(
(screen: keyof RootStackParamList, params?: any) => {
setNavigationHistory((prev) => [...prev, screen])
navigation.navigate(screen as never, params as never)
},
[navigation]
)
const goBackInHistory = useCallback(() => {
if (navigationHistory.length > 1) {
const prevScreen = navigationHistory[navigationHistory.length - 2]
setNavigationHistory((prev) => prev.slice(0, -1))
navigation.navigate(prevScreen as never)
} else {
navigation.goBack()
}
}, [navigation, navigationHistory])
return { navigateWithHistory, goBackInHistory, navigationHistory }
}
// Deep linking with complex routing
const useDeepLinking = () => {
const navigation = useNavigation()
useEffect(() => {
const handleURL = (url: string) => {
const route = Linking.parse(url)
if (route.pathname === "/profile" && route.queryParams?.userId) {
navigation.navigate("Profile", {
userId: route.queryParams.userId as string,
})
} else if (route.pathname === "/settings") {
navigation.navigate("Settings", {
section: route.queryParams?.section as string,
})
}
}
Linking.getInitialURL().then((url) => {
if (url) handleURL(url)
})
const subscription = Linking.addEventListener("url", ({ url }) => {
handleURL(url)
})
return () => subscription?.remove()
}, [navigation])
}
Microinteractions
Advanced Gesture Handling
// Complex gesture patterns
const useAdvancedGestures = () => {
const pan = Gesture.Pan()
.onStart(() => {
runOnJS(hapticFeedback)()
})
.onUpdate((event) => {
translateX.value = event.translationX
translateY.value = event.translationY
// Dynamic resistance
const resistance = Math.abs(event.translationX) / 100
scale.value = Math.max(0.8, 1 - resistance * 0.2)
})
.onEnd((event) => {
const shouldDismiss = Math.abs(event.translationX) > 100 || Math.abs(event.velocityX) > 500
if (shouldDismiss) {
translateX.value = withSpring(event.translationX > 0 ? 300 : -300, { velocity: event.velocityX }, () =>
runOnJS(onDismiss)()
)
} else {
translateX.value = withSpring(0)
translateY.value = withSpring(0)
scale.value = withSpring(1)
}
})
const longPress = Gesture.LongPress()
.minDuration(500)
.onStart(() => {
scale.value = withSpring(1.1)
runOnJS(triggerHaptic)("impactHeavy")
})
.onEnd(() => {
scale.value = withSpring(1)
runOnJS(showContextMenu)()
})
return Gesture.Simultaneous(pan, longPress)
}
// Physics-based animations
const usePhysicsAnimation = () => {
const position = useSharedValue({ x: 0, y: 0 })
const velocity = useSharedValue({ x: 0, y: 0 })
const animateToPosition = (targetX: number, targetY: number) => {
const distance = Math.sqrt(Math.pow(targetX - position.value.x, 2) + Math.pow(targetY - position.value.y, 2))
const duration = Math.min(1000, distance * 2)
position.value = withTiming(
{ x: targetX, y: targetY },
{
duration,
easing: Easing.out(Easing.cubic),
}
)
}
const addImpulse = (impulseX: number, impulseY: number) => {
velocity.value = {
x: velocity.value.x + impulseX,
y: velocity.value.y + impulseY,
}
// Apply physics simulation
const simulate = () => {
const friction = 0.95
velocity.value = {
x: velocity.value.x * friction,
y: velocity.value.y * friction,
}
position.value = {
x: position.value.x + velocity.value.x,
y: position.value.y + velocity.value.y,
}
if (Math.abs(velocity.value.x) > 0.1 || Math.abs(velocity.value.y) > 0.1) {
requestAnimationFrame(simulate)
}
}
simulate()
}
return { position, animateToPosition, addImpulse }
}
Code Splitting
Dynamic Imports and Lazy Loading
// Component lazy loading
const LazyScreen = React.lazy(() =>
import('../screens/ExpensiveScreen').then(module => ({
default: module.ExpensiveScreen
}))
);
const LazyScreenWrapper = () => (
<Suspense fallback={<LoadingSpinner />}>
<LazyScreen />
</Suspense>
);
// Bundle splitting with Metro
const useDynamicImport = <T>(importFn: () => Promise<T>) => {
const [module, setModule] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const load = useCallback(async () => {
if (module) return module;
setLoading(true);
setError(null);
try {
const loadedModule = await importFn();
setModule(loadedModule);
return loadedModule;
} catch (err) {
setError(err as Error);
throw err;
} finally {
setLoading(false);
}
}, [importFn, module]);
return { module, loading, error, load };
};
// Usage
const useAdvancedFeatures = () => {
const { module: advancedModule, load } = useDynamicImport(() =>
import('../modules/AdvancedFeatures')
);
const enableAdvancedMode = async () => {
const module = await load();
module.initialize();
};
return { advancedModule, enableAdvancedMode };
};
Advanced Debugging
Performance Profiling
// Custom performance monitor
class PerformanceMonitor {
private static instance: PerformanceMonitor
private metrics: Map<string, number[]> = new Map()
static getInstance() {
if (!this.instance) {
this.instance = new PerformanceMonitor()
}
return this.instance
}
startMeasure(label: string) {
const start = performance.now()
return () => {
const duration = performance.now() - start
this.addMetric(label, duration)
return duration
}
}
private addMetric(label: string, value: number) {
if (!this.metrics.has(label)) {
this.metrics.set(label, [])
}
this.metrics.get(label)!.push(value)
}
getAverageTime(label: string): number {
const values = this.metrics.get(label) || []
return values.reduce((sum, val) => sum + val, 0) / values.length
}
getReport(): Record<string, { avg: number; count: number }> {
const report: Record<string, { avg: number; count: number }> = {}
this.metrics.forEach((values, label) => {
report[label] = {
avg: this.getAverageTime(label),
count: values.length,
}
})
return report
}
}
// Performance monitoring hook
const usePerformanceMonitor = (label: string) => {
const monitor = PerformanceMonitor.getInstance()
const measurePerformance = useCallback(
(fn: () => void | Promise<void>) => {
const stopMeasure = monitor.startMeasure(label)
if (fn.constructor.name === "AsyncFunction") {
return (fn as () => Promise<void>)().finally(stopMeasure)
} else {
const result = fn()
stopMeasure()
return result
}
},
[label, monitor]
)
return measurePerformance
}
// Memory usage tracking
const useMemoryMonitor = () => {
const [memoryUsage, setMemoryUsage] = useState<{
used: number
total: number
}>({ used: 0, total: 0 })
useEffect(() => {
const updateMemoryUsage = () => {
if ("memory" in performance) {
const memory = (performance as any).memory
setMemoryUsage({
used: memory.usedJSHeapSize / 1024 / 1024, // MB
total: memory.totalJSHeapSize / 1024 / 1024, // MB
})
}
}
const interval = setInterval(updateMemoryUsage, 1000)
updateMemoryUsage()
return () => clearInterval(interval)
}, [])
return memoryUsage
}
Next Steps
After mastering these advanced topics:
- Performance Optimization - Apply advanced optimization techniques
- Deployment & Distribution - Deploy your advanced features
- Testing Strategies - Test complex functionality thoroughly