React Native App Development in 2026: Complete Guide and Interview Questions

Master React Native app development in 2026 with the New Architecture, Hermes V1, and Fabric. Includes practical code examples and common interview questions.

React Native app development architecture diagram showing cross-platform mobile development workflow

React Native app development in 2026 has fundamentally changed with version 0.87 now shipping the New Architecture as the only supported option. The legacy bridge that once serialized every JavaScript-to-native call as JSON is gone, replaced by JSI (JavaScript Interface) for direct synchronous communication.

What Changed in React Native 0.87

React Native 0.87 makes the New Architecture mandatory. The old bridge is completely removed. Complex list rendering runs 43% faster, scroll frame drops decreased by 95%, and memory usage dropped 33% in benchmark tests.

Understanding JSI and the New Architecture

JSI (JavaScript Interface) replaces the asynchronous bridge with direct C++ bindings between JavaScript and native code. Instead of serializing data to JSON, passing it through a message queue, and deserializing on the other side, JSI allows JavaScript to hold references to native objects and call their methods directly.

This architectural shift enables three core capabilities: synchronous native calls, shared ownership of objects between JavaScript and native layers, and lazy loading of native modules.

NativeUserModule.tstypescript
import { TurboModuleRegistry, TurboModule } from 'react-native';

export interface Spec extends TurboModule {
  getUserProfile(userId: string): Promise<{
    id: string;
    name: string;
    email: string;
  }>;
  
  // Synchronous call - only possible with JSI
  getDeviceLocale(): string;
}

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

The getDeviceLocale() method returns immediately without awaiting a Promise. This was impossible with the old bridge where every native call required async serialization.

Building a Production-Ready Component with Fabric

Fabric is the new rendering system that works alongside JSI. It manages the UI tree in C++ and synchronizes directly with the JavaScript thread, eliminating the "UI jank" that occurred when the old bridge fell behind during rapid updates.

ProductCard.tsxtypescript
import React, { memo, useCallback } from 'react';
import {
  View,
  Text,
  Image,
  Pressable,
  StyleSheet,
} from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  inStock: boolean;
}

interface ProductCardProps {
  product: Product;
  onPress: (id: string) => void;
}

const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

export const ProductCard = memo(function ProductCard({
  product,
  onPress,
}: ProductCardProps) {
  const scale = useSharedValue(1);

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

  const handlePressIn = useCallback(() => {
    scale.value = withSpring(0.95);
  }, [scale]);

  const handlePressOut = useCallback(() => {
    scale.value = withSpring(1);
  }, [scale]);

  const handlePress = useCallback(() => {
    onPress(product.id);
  }, [onPress, product.id]);

  return (
    <AnimatedPressable
      style={[styles.card, animatedStyle]}
      onPressIn={handlePressIn}
      onPressOut={handlePressOut}
      onPress={handlePress}
      accessibilityRole="button"
      accessibilityLabel={`${product.name}, ${product.price} dollars`}
    >
      <Image
        source={{ uri: product.imageUrl }}
        style={styles.image}
        resizeMode="cover"
      />
      <View style={styles.content}>
        <Text style={styles.name} numberOfLines={2}>
          {product.name}
        </Text>
        <Text style={styles.price}>${product.price.toFixed(2)}</Text>
        {!product.inStock && (
          <Text style={styles.outOfStock}>Out of Stock</Text>
        )}
      </View>
    </AnimatedPressable>
  );
});

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    overflow: 'hidden',
    marginHorizontal: 8,
    marginVertical: 4,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  image: {
    width: '100%',
    height: 160,
  },
  content: {
    padding: 12,
  },
  name: {
    fontSize: 16,
    fontWeight: '600',
    color: '#1a1a1a',
    marginBottom: 4,
  },
  price: {
    fontSize: 18,
    fontWeight: '700',
    color: '#2563eb',
  },
  outOfStock: {
    fontSize: 12,
    color: '#dc2626',
    marginTop: 4,
  },
});

This component uses react-native-reanimated which runs animations on the UI thread via JSI. The useSharedValue and useAnimatedStyle hooks communicate directly with native code without crossing the bridge.

State Management with TanStack Query

Server state in React Native applications benefits from dedicated tooling. TanStack Query handles caching, background refetching, and optimistic updates without requiring a global state container.

hooks/useProducts.tstypescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

interface Product {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  inStock: boolean;
}

const API_BASE = 'https://api.example.com';

async function fetchProducts(category?: string): Promise<Product[]> {
  const url = category
    ? `${API_BASE}/products?category=${category}`
    : `${API_BASE}/products`;
  
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error('Failed to fetch products');
  }
  return response.json();
}

export function useProducts(category?: string) {
  return useQuery({
    queryKey: ['products', category],
    queryFn: () => fetchProducts(category),
    staleTime: 5 * 60 * 1000, // 5 minutes
    gcTime: 30 * 60 * 1000, // 30 minutes
  });
}

export function useToggleStock() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (productId: string) => {
      const response = await fetch(`${API_BASE}/products/${productId}/toggle-stock`, {
        method: 'POST',
      });
      if (!response.ok) {
        throw new Error('Failed to update stock');
      }
      return response.json();
    },
    // Optimistic update
    onMutate: async (productId) => {
      await queryClient.cancelQueries({ queryKey: ['products'] });
      
      const previousProducts = queryClient.getQueryData<Product[]>(['products']);
      
      queryClient.setQueryData<Product[]>(['products'], (old) =>
        old?.map((p) =>
          p.id === productId ? { ...p, inStock: !p.inStock } : p
        )
      );
      
      return { previousProducts };
    },
    onError: (_err, _productId, context) => {
      if (context?.previousProducts) {
        queryClient.setQueryData(['products'], context.previousProducts);
      }
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['products'] });
    },
  });
}

The onMutate callback performs an optimistic update before the server responds. If the mutation fails, onError restores the previous state. This pattern delivers instant UI feedback while maintaining data consistency.

Ready to ace your React Native interviews?

Practice with our interactive simulators, flashcards, and technical tests.

Expo Router provides file-based routing for React Native, similar to Next.js App Router. The file structure defines routes automatically.

app/(tabs)/_layout.tsxtypescript
import { Tabs } from 'expo-router';
import { Home, Search, ShoppingCart, User } from 'lucide-react-native';

export default function TabsLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#2563eb',
        tabBarInactiveTintColor: '#6b7280',
        headerShown: false,
      }}
    >
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Home color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Search',
          tabBarIcon: ({ color, size }) => (
            <Search color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="cart"
        options={{
          title: 'Cart',
          tabBarIcon: ({ color, size }) => (
            <ShoppingCart color={color} size={size} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <User color={color} size={size} />
          ),
        }}
      />
    </Tabs>
  );
}

Dynamic routes use bracket notation. A file at app/product/[id].tsx handles any /product/123 path:

app/product/[id].tsxtypescript
import { useLocalSearchParams } from 'expo-router';
import { View, Text, ActivityIndicator } from 'react-native';
import { useQuery } from '@tanstack/react-query';

export default function ProductDetailScreen() {
  const { id } = useLocalSearchParams<{ id: string }>();

  const { data: product, isLoading, error } = useQuery({
    queryKey: ['product', id],
    queryFn: () => fetchProduct(id),
    enabled: !!id,
  });

  if (isLoading) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <ActivityIndicator size="large" color="#2563eb" />
      </View>
    );
  }

  if (error || !product) {
    return (
      <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
        <Text>Product not found</Text>
      </View>
    );
  }

  return (
    <View style={{ flex: 1, padding: 16 }}>
      <Text style={{ fontSize: 24, fontWeight: 'bold' }}>{product.name}</Text>
      <Text style={{ fontSize: 20, color: '#2563eb' }}>
        ${product.price.toFixed(2)}
      </Text>
    </View>
  );
}

Common Interview Questions on React Native Architecture

Technical interviews for React Native positions in 2026 focus heavily on the New Architecture. The following questions appear frequently.

Interview Focus Areas

Interviewers expect candidates to explain JSI, Fabric, and TurboModules at a conceptual level. Memorizing syntax matters less than understanding why the architecture changed and what problems it solves.

Question: What is JSI and why did React Native adopt it?

JSI (JavaScript Interface) is a C++ layer that allows JavaScript to hold references to native objects and call their methods directly. The old bridge serialized every call to JSON, queued it asynchronously, and deserialized on the native side. This added latency and prevented synchronous operations.

With JSI, a JavaScript function can call a native method and receive the result in the same frame. This enables features like synchronous layout measurements and direct manipulation of native views.

Question: How does Fabric differ from the old renderer?

Fabric is a C++ rendering system that maintains the UI tree in native code rather than JavaScript. The old renderer kept the shadow tree in JavaScript and sent update batches across the bridge.

Fabric benefits:

  • Synchronous layout calculations via JSI
  • Concurrent rendering support (React 18 features work properly)
  • Better memory management with shared ownership between JS and native
  • Reduced serialization overhead

Question: What are TurboModules?

TurboModules are the replacement for Native Modules. The key differences:

  1. Lazy loading: TurboModules load only when first accessed, reducing startup time
  2. Type safety: CodeGen generates type-safe bindings from TypeScript specs
  3. Synchronous methods: TurboModules can expose synchronous functions when needed
  4. Direct JSI access: No bridge serialization

Question: When would you use a synchronous native method?

Synchronous native methods suit operations that:

  • Complete in microseconds (reading device locale, checking permissions status)
  • Block UI intentionally (modal confirmation before navigation)
  • Return values needed for immediate rendering

Avoid synchronous methods for:

  • Network requests
  • File I/O beyond small reads
  • Database queries
  • Any operation exceeding 16ms (blocks the frame)

For more React Native interview questions, see the networking and API module and native modules section.

Testing React Native Components

React Native Testing Library provides utilities for testing components as users interact with them, focusing on accessibility and behavior rather than implementation details.

__tests__/ProductCard.test.tsxtypescript
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react-native';
import { ProductCard } from '../components/ProductCard';

const mockProduct = {
  id: 'prod-1',
  name: 'Wireless Headphones',
  price: 149.99,
  imageUrl: 'https://example.com/headphones.jpg',
  inStock: true,
};

describe('ProductCard', () => {
  it('displays product information', () => {
    const onPress = jest.fn();
    render(<ProductCard product={mockProduct} onPress={onPress} />);

    expect(screen.getByText('Wireless Headphones')).toBeOnTheScreen();
    expect(screen.getByText('$149.99')).toBeOnTheScreen();
  });

  it('calls onPress with product id when pressed', () => {
    const onPress = jest.fn();
    render(<ProductCard product={mockProduct} onPress={onPress} />);

    fireEvent.press(
      screen.getByRole('button', { name: /wireless headphones/i })
    );

    expect(onPress).toHaveBeenCalledWith('prod-1');
    expect(onPress).toHaveBeenCalledTimes(1);
  });

  it('shows out of stock message when product unavailable', () => {
    const outOfStockProduct = { ...mockProduct, inStock: false };
    render(<ProductCard product={outOfStockProduct} onPress={jest.fn()} />);

    expect(screen.getByText('Out of Stock')).toBeOnTheScreen();
  });
});

The tests query by text content and accessibility roles rather than test IDs or component internals. This approach catches regressions that affect users while remaining resilient to refactoring.

Performance Monitoring in Production

React Native 0.87 integrates with Sentry and similar tools for production performance monitoring. Key metrics to track:

  • Time to Interactive (TTI): How long until the app responds to user input
  • Frame drops: Frames that take longer than 16.67ms to render
  • JavaScript thread usage: High usage indicates expensive computations blocking the UI
  • Memory growth: Steady increases suggest leaks
app/_layout.tsxtypescript
import * as Sentry from '@sentry/react-native';
import { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native';

Sentry.init({
  dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.2,
  profilesSampleRate: 0.1,
  enableAutoPerformanceTracing: true,
});

export default function RootLayout() {
  useEffect(() => {
    const subscription = AppState.addEventListener(
      'change',
      (state: AppStateStatus) => {
        if (state === 'active') {
          Sentry.addBreadcrumb({
            category: 'app.lifecycle',
            message: 'App became active',
            level: 'info',
          });
        }
      }
    );

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

  // ... rest of layout
}

Key Takeaways for React Native Development in 2026

  • The New Architecture (JSI, Fabric, TurboModules) is mandatory in React Native 0.87. The legacy bridge no longer exists.
  • JSI enables synchronous native calls. Use them sparingly for operations under 16ms.
  • Fabric renders UI in C++ and synchronizes with JavaScript via JSI. This eliminates the async rendering bottleneck.
  • TurboModules load lazily and provide type-safe bindings generated from TypeScript specs.
  • Expo Router offers file-based navigation similar to Next.js App Router.
  • React Native Testing Library tests components through their public API and accessibility tree.
  • Production apps should track TTI, frame drops, and memory usage from day one.

For a deeper look at building complete applications, see the React Native complete app tutorial. The New Architecture guide covers migration from older versions.

Start practicing!

Test your knowledge with our interview simulators and technical tests.

Daily challenge

Can you spot the bug in React Native?

One real snippet, one hidden bug, one attempt a day. No account needed to try.

Anthony Fillion-Maillet

Written by

Anthony Fillion-Maillet

Founder of SharpSkill

Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.

Updated on September 6, 2026

Tags

#react-native
#mobile-development
#javascript
#typescript
#new-architecture

Share

Related articles