Skip to main content

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

  1. Custom Native Modules
  2. Bridge Communication
  3. Native UI Components
  4. Advanced Styling
  5. Custom Hooks & Patterns
  6. Memory Management
  7. Advanced Navigation
  8. Microinteractions
  9. Code Splitting
  10. 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:

Resources