Back to Blog
React Native in 2024: The Complete Developer's Guide

React Native in 2024: The Complete Developer's Guide

Ansh Gupta
9 min read
React NativeMobile DevelopmentReactiOSAndroidCross-platform

React Native in 2024: The Complete Developer's Guide

React Native has evolved tremendously in 2024, bringing the New Architecture, improved performance, and better developer experience. Let's explore everything that makes React Native the top choice for cross-platform mobile development.

What's New in React Native 2024

React Native 0.74+ introduces groundbreaking changes that enhance performance, developer experience, and app capabilities.

1. New Architecture (Fabric & TurboModules)

The New Architecture is now stable and production-ready, bringing significant performance improvements.

Fabric Renderer

// components/OptimizedList.jsx
import React from 'react';
import { FlatList, Text, View } from 'react-native';

const OptimizedList = ({ data }) => {
  const renderItem = ({ item }) => (
    <View style={styles.item}>
      <Text style={styles.title}>{item.title}</Text>
      <Text style={styles.description}>{item.description}</Text>
    </View>
  );

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      // New Architecture optimizations
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      windowSize={10}
      initialNumToRender={10}
      getItemLayout={(data, index) => ({
        length: 80,
        offset: 80 * index,
        index,
      })}
    />
  );
};

TurboModules for Native Performance

// Create a TurboModule
// NativeCalculator.js
import { TurboModule, TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  add(a: number, b: number): Promise<number>;
  multiply(a: number, b: number): number;
}

export default TurboModuleRegistry.getEnforcing<Spec>('Calculator');

// Usage in components
import Calculator from './NativeCalculator';

function MathComponent() {
  const [result, setResult] = useState(0);
  
  const performCalculation = async () => {
    const sum = await Calculator.add(5, 10);
    const product = Calculator.multiply(3, 4);
    setResult(sum + product);
  };
  
  return (
    <View>
      <Text>Result: {result}</Text>
      <Button title="Calculate" onPress={performCalculation} />
    </View>
  );
}

2. Expo Router - File-Based Navigation

Expo Router brings Next.js-style file-based routing to React Native.

# Project structure
app/
  _layout.tsx
  index.tsx
  (tabs)/
    _layout.tsx
    home.tsx
    profile.tsx
  [user].tsx
  modal.tsx
// app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="modal" options={{ presentation: 'modal' }} />
      <Stack.Screen name="[user]" options={{ title: 'User Profile' }} />
    </Stack>
  );
}

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen
        name="home"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => (
            <Ionicons name="home" size={24} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color }) => (
            <Ionicons name="person" size={24} color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

// app/[user].tsx
import { useLocalSearchParams } from 'expo-router';
import { Text, View } from 'react-native';

export default function UserProfile() {
  const { user } = useLocalSearchParams();
  
  return (
    <View>
      <Text>User Profile: {user}</Text>
    </View>
  );
}

3. Modern State Management with Zustand

Zustand provides simple, fast state management for React Native apps.

// stores/useAppStore.js
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

const useAppStore = create(
  persist(
    (set, get) => ({
      // User state
      user: null,
      isAuthenticated: false,
      
      // App preferences
      theme: 'light',
      language: 'en',
      
      // Actions
      login: (userData) => set({ user: userData, isAuthenticated: true }),
      logout: () => set({ user: null, isAuthenticated: false }),
      
      setTheme: (theme) => set({ theme }),
      setLanguage: (language) => set({ language }),
      
      // Async actions
      fetchUserProfile: async (userId) => {
        try {
          const response = await fetch(`/api/users/${userId}`);
          const userData = await response.json();
          set({ user: userData });
        } catch (error) {
          console.error('Failed to fetch user:', error);
        }
      },
    }),
    {
      name: 'app-storage',
      storage: createJSONStorage(() => AsyncStorage),
      partialize: (state) => ({
        theme: state.theme,
        language: state.language,
      }),
    }
  )
);

// Usage in components
import { useAppStore } from '../stores/useAppStore';

function ProfileScreen() {
  const { user, theme, setTheme, fetchUserProfile } = useAppStore();
  
  useEffect(() => {
    if (!user) {
      fetchUserProfile('123');
    }
  }, [user, fetchUserProfile]);
  
  return (
    <View style={[styles.container, { backgroundColor: theme === 'dark' ? '#000' : '#fff' }]}>
      <Text>{user?.name}</Text>
      <Button 
        title={`Switch to ${theme === 'light' ? 'Dark' : 'Light'} Theme`}
        onPress={() => setTheme(theme === 'light' ? 'dark' : 'light')}
      />
    </View>
  );
}

4. Advanced Animations with Reanimated 3

React Native Reanimated 3 provides native-level animations.

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  withTiming,
  runOnJS,
} from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';

function SwipeableCard({ onSwipe, children }) {
  const translateX = useSharedValue(0);
  const opacity = useSharedValue(1);
  
  const panGesture = Gesture.Pan()
    .onUpdate((event) => {
      translateX.value = event.translationX;
      opacity.value = 1 - Math.abs(event.translationX) / 300;
    })
    .onEnd((event) => {
      const shouldSwipe = Math.abs(event.translationX) > 100;
      
      if (shouldSwipe) {
        const direction = event.translationX > 0 ? 1 : -1;
        translateX.value = withTiming(direction * 300, { duration: 200 }, () => {
          runOnJS(onSwipe)(direction);
        });
        opacity.value = withTiming(0, { duration: 200 });
      } else {
        translateX.value = withSpring(0);
        opacity.value = withSpring(1);
      }
    });

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
    opacity: opacity.value,
  }));

  return (
    <GestureDetector gesture={panGesture}>
      <Animated.View style={[styles.card, animatedStyle]}>
        {children}
      </Animated.View>
    </GestureDetector>
  );
}

// Advanced loading animation
function LoadingSpinner() {
  const rotation = useSharedValue(0);
  
  useEffect(() => {
    rotation.value = withTiming(360, { duration: 1000 }, () => {
      rotation.value = 0;
    });
  }, []);
  
  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ rotate: `${rotation.value}deg` }],
  }));
  
  return (
    <Animated.View style={[styles.spinner, animatedStyle]}>
      <Text>🔄</Text>
    </Animated.View>
  );
}

5. Performance Optimization Techniques

Optimized Image Loading

import FastImage from 'react-native-fast-image';
import { useState } from 'react';

function OptimizedImage({ uri, style }) {
  const [loading, setLoading] = useState(true);
  
  return (
    <View style={style}>
      <FastImage
        style={{ flex: 1 }}
        source={{
          uri,
          priority: FastImage.priority.normal,
          cache: FastImage.cacheControl.immutable,
        }}
        resizeMode={FastImage.resizeMode.cover}
        onLoadStart={() => setLoading(true)}
        onLoadEnd={() => setLoading(false)}
      />
      {loading && (
        <View style={styles.loadingOverlay}>
          <ActivityIndicator size="small" color="#007AFF" />
        </View>
      )}
    </View>
  );
}

// Lazy loading for lists
function LazyImageList({ images }) {
  const [visibleImages, setVisibleImages] = useState(new Set());
  
  const onViewableItemsChanged = ({ viewableItems }) => {
    const visible = new Set(viewableItems.map(item => item.key));
    setVisibleImages(visible);
  };
  
  const renderItem = ({ item, index }) => (
    <View style={styles.imageContainer}>
      {visibleImages.has(item.id) ? (
        <OptimizedImage uri={item.url} style={styles.image} />
      ) : (
        <View style={styles.placeholder} />
      )}
    </View>
  );
  
  return (
    <FlatList
      data={images}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      onViewableItemsChanged={onViewableItemsChanged}
      viewabilityConfig={{ itemVisiblePercentThreshold: 50 }}
    />
  );
}

6. Native Module Integration

iOS Native Module (Swift)

// ios/MyApp/CalculatorModule.swift
import React

@objc(CalculatorModule)
class CalculatorModule: NSObject {
  
  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }
  
  @objc
  func add(_ a: NSNumber, b: NSNumber, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) {
    let result = a.doubleValue + b.doubleValue
    resolver(result)
  }
  
  @objc
  func multiply(_ a: NSNumber, b: NSNumber) -> NSNumber {
    return NSNumber(value: a.doubleValue * b.doubleValue)
  }
}

// ios/MyApp/CalculatorModule.m
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(CalculatorModule, NSObject)

RCT_EXTERN_METHOD(add:(NSNumber *)a 
                 b:(NSNumber *)b 
                 resolver:(RCTPromiseResolveBlock)resolve 
                 rejecter:(RCTPromiseRejectBlock)reject)

RCT_EXTERN_METHOD(multiply:(NSNumber *)a b:(NSNumber *)b)

@end

Android Native Module (Kotlin)

// android/app/src/main/java/com/myapp/CalculatorModule.kt
package com.myapp

import com.facebook.react.bridge.*

class CalculatorModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    
    override fun getName(): String {
        return "CalculatorModule"
    }
    
    @ReactMethod
    fun add(a: Double, b: Double, promise: Promise) {
        try {
            val result = a + b
            promise.resolve(result)
        } catch (e: Exception) {
            promise.reject("CALCULATION_ERROR", e.message)
        }
    }
    
    @ReactMethod(isBlockingSynchronousMethod = true)
    fun multiply(a: Double, b: Double): Double {
        return a * b
    }
}

// Register the module
class CalculatorPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(CalculatorModule(reactContext))
    }
    
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

7. Testing Best Practices

Component Testing with React Native Testing Library

// __tests__/LoginForm.test.js
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react-native';
import { LoginForm } from '../components/LoginForm';

describe('LoginForm', () => {
  const mockLogin = jest.fn();
  
  beforeEach(() => {
    mockLogin.mockClear();
  });
  
  it('should submit form with valid credentials', async () => {
    const { getByTestId } = render(<LoginForm onLogin={mockLogin} />);
    
    const emailInput = getByTestId('email-input');
    const passwordInput = getByTestId('password-input');
    const submitButton = getByTestId('submit-button');
    
    fireEvent.changeText(emailInput, 'test@example.com');
    fireEvent.changeText(passwordInput, 'password123');
    fireEvent.press(submitButton);
    
    await waitFor(() => {
      expect(mockLogin).toHaveBeenCalledWith({
        email: 'test@example.com',
        password: 'password123',
      });
    });
  });
  
  it('should show error for invalid email', () => {
    const { getByTestId, getByText } = render(<LoginForm onLogin={mockLogin} />);
    
    const emailInput = getByTestId('email-input');
    const submitButton = getByTestId('submit-button');
    
    fireEvent.changeText(emailInput, 'invalid-email');
    fireEvent.press(submitButton);
    
    expect(getByText('Please enter a valid email')).toBeTruthy();
  });
});

// E2E Testing with Detox
// e2e/loginFlow.e2e.js
describe('Login Flow', () => {
  beforeAll(async () => {
    await device.launchApp();
  });
  
  beforeEach(async () => {
    await device.reloadReactNative();
  });
  
  it('should login successfully', async () => {
    await element(by.id('email-input')).typeText('test@example.com');
    await element(by.id('password-input')).typeText('password123');
    await element(by.id('login-button')).tap();
    
    await waitFor(element(by.id('home-screen')))
      .toBeVisible()
      .withTimeout(5000);
  });
});

8. Build and Deployment

EAS Build Configuration

// eas.json
{
  "cli": {
    "version": ">= 5.0.0"
  },
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal"
    },
    "preview": {
      "distribution": "internal",
      "ios": {
        "simulator": true
      }
    },
    "production": {
      "env": {
        "NODE_ENV": "production"
      }
    }
  },
  "submit": {
    "production": {}
  }
}

// app.config.js
export default {
  expo: {
    name: "My App",
    slug: "my-app",
    version: "1.0.0",
    orientation: "portrait",
    icon: "./assets/icon.png",
    userInterfaceStyle: "automatic",
    splash: {
      image: "./assets/splash.png",
      resizeMode: "contain",
      backgroundColor: "#ffffff"
    },
    updates: {
      fallbackToCacheTimeout: 0
    },
    assetBundlePatterns: ["**/*"],
    ios: {
      supportsTablet: true,
      bundleIdentifier: "com.company.myapp"
    },
    android: {
      adaptiveIcon: {
        foregroundImage: "./assets/adaptive-icon.png",
        backgroundColor: "#FFFFFF"
      },
      package: "com.company.myapp"
    },
    plugins: [
      "expo-router",
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
        }
      ]
    ]
  }
};

9. Security Best Practices

// utils/secureStorage.js
import * as SecureStore from 'expo-secure-store';
import CryptoJS from 'crypto-js';

const SECRET_KEY = 'your-secret-key'; // Should be from environment variables

export const secureStorage = {
  async setItem(key, value) {
    try {
      const encrypted = CryptoJS.AES.encrypt(JSON.stringify(value), SECRET_KEY).toString();
      await SecureStore.setItemAsync(key, encrypted);
    } catch (error) {
      console.error('Failed to store secure item:', error);
    }
  },
  
  async getItem(key) {
    try {
      const encrypted = await SecureStore.getItemAsync(key);
      if (!encrypted) return null;
      
      const bytes = CryptoJS.AES.decrypt(encrypted, SECRET_KEY);
      const decrypted = bytes.toString(CryptoJS.enc.Utf8);
      return JSON.parse(decrypted);
    } catch (error) {
      console.error('Failed to retrieve secure item:', error);
      return null;
    }
  },
  
  async removeItem(key) {
    try {
      await SecureStore.deleteItemAsync(key);
    } catch (error) {
      console.error('Failed to remove secure item:', error);
    }
  }
};

// Certificate pinning
import { NetworkingModule } from 'react-native';

const setupCertificatePinning = () => {
  NetworkingModule.addRequestInterceptor((request) => {
    // Add certificate validation logic
    return request;
  });
};

Performance Monitoring

// utils/performance.js
import { Performance } from 'react-native-performance';
import crashlytics from '@react-native-firebase/crashlytics';

export const performanceMonitor = {
  startTrace(name) {
    return Performance.mark(`${name}-start`);
  },
  
  endTrace(name) {
    Performance.mark(`${name}-end`);
    Performance.measure(name, `${name}-start`, `${name}-end`);
  },
  
  logError(error, context = {}) {
    crashlytics().recordError(error);
    crashlytics().setAttributes(context);
  },
  
  setUserId(userId) {
    crashlytics().setUserId(userId);
  }
};

// Usage in components
function ExpensiveComponent() {
  useEffect(() => {
    performanceMonitor.startTrace('expensive-render');
    
    return () => {
      performanceMonitor.endTrace('expensive-render');
    };
  }, []);
  
  // Component logic
}

Conclusion

React Native in 2024 offers:

  • New Architecture for native-level performance
  • Expo Router for modern navigation patterns
  • Advanced animations with Reanimated 3
  • Better state management with modern libraries
  • Improved developer experience with better tooling
  • Enhanced security and performance monitoring

React Native continues to be the best choice for cross-platform mobile development, offering native performance with React's developer experience.

Getting Started

# Create new React Native app with Expo
npx create-expo-app@latest MyApp --template tabs

# Navigate to project
cd MyApp

# Start development server
npx expo start

The future of mobile development is here with React Native 2024! 🚀

AG

About Ansh Gupta

Frontend Developer with 3 years of experience building modern web applications. Based in Indore, India, passionate about React, TypeScript, and creating exceptional user experiences.

Learn more about me