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 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.
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.
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.
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.
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.
Navigation with Expo Router
Expo Router provides file-based routing for React Native, similar to Next.js App Router. The file structure defines routes automatically.
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:
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.
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:
- Lazy loading: TurboModules load only when first accessed, reducing startup time
- Type safety: CodeGen generates type-safe bindings from TypeScript specs
- Synchronous methods: TurboModules can expose synchronous functions when needed
- 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.
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
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.
Can you spot the bug in React Native?
One real snippet, one hidden bug, one attempt a day. No account needed to try.

Written by
Anthony Fillion-MailletFounder of SharpSkill
Full-stack developer for over 10 years. Runs SharpSkill and answers for everything published here.
Updated on September 6, 2026
Tags
Share
Related articles

React Native and TypeScript in 2026: Type-Safe Architecture and Interview Questions
Build type-safe React Native apps with TypeScript, Codegen, TurboModules, and the Strict TypeScript API. Covers architecture patterns, typed navigation, toolchain requirements for 0.87, and interview questions.

React Native App Development Guide 2026: Building Production Apps and Interview Questions
Complete guide to React Native production development in 2026 covering Expo SDK 56, EAS Build, Hermes V1, the New Architecture, and interview preparation for React Native developers.

React Native 0.87 and SwiftPM in 2026: Modern iOS Build and Interview Questions
React Native 0.87 introduces experimental Swift Package Manager support, eliminating CocoaPods from iOS builds. This guide covers SwiftPM setup, the Strict TypeScript API, Metro 0.87 optimizations, and interview questions for senior mobile developers.