Flutter and React Native continue to dominate cross-platform mobile development in 2026. Both frameworks have matured significantly, with React Native's New Architecture and Flutter's Impeller rendering engine closing the performance gap with native development. This guide provides an in-depth comparison to help you choose the right framework.
Quick Comparison
- Flutter: Dart language, custom rendering engine, excellent performance, consistent UI across platforms
- React Native: JavaScript/TypeScript, native components, large ecosystem, easier web developer transition
React Native with New Architecture
React Native's New Architecture (Fabric + TurboModules + JSI) has transformed performance. Synchronous native calls, concurrent rendering, and improved memory management make it viable for demanding applications.
// React Native - Modern Component with TypeScript
import React, { useCallback, useMemo } from 'react';
import {
View,
Text,
FlatList,
StyleSheet,
Pressable,
useWindowDimensions,
} from 'react-native';
import { useQuery } from '@tanstack/react-query';
import Animated, {
useAnimatedStyle,
withSpring,
useSharedValue,
} from 'react-native-reanimated';
interface Product {
id: string;
name: string;
price: number;
image: string;
}
export function ProductGrid() {
const { width } = useWindowDimensions();
const numColumns = width > 600 ? 3 : 2;
const { data: products, isLoading } = useQuery({
queryKey: ['products'],
queryFn: fetchProducts,
});
const renderItem = useCallback(
({ item, index }: { item: Product; index: number }) => (
<ProductCard product={item} index={index} />
),
[]
);
const keyExtractor = useCallback((item: Product) => item.id, []);
if (isLoading) {
return <ProductGridSkeleton numColumns={numColumns} />;
}
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
numColumns={numColumns}
key={numColumns} // Force re-render on column change
contentContainerStyle={styles.container}
showsVerticalScrollIndicator={false}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
windowSize={5}
initialNumToRender={6}
getItemLayout={(_, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * Math.floor(index / numColumns),
index,
})}
/>
);
}
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
function ProductCard({ product, index }: { product: Product; index: number }) {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const onPressIn = useCallback(() => {
scale.value = withSpring(0.95);
}, []);
const onPressOut = useCallback(() => {
scale.value = withSpring(1);
}, []);
return (
<AnimatedPressable
style={[styles.card, animatedStyle]}
onPressIn={onPressIn}
onPressOut={onPressOut}
>
<Animated.Image
source={{ uri: product.image }}
style={styles.image}
sharedTransitionTag={`product-${product.id}`}
/>
<View style={styles.cardContent}>
<Text style={styles.productName} numberOfLines={2}>
{product.name}
</Text>
<Text style={styles.productPrice}>
${product.price.toFixed(2)}
</Text>
</View>
</AnimatedPressable>
);
}
const ITEM_HEIGHT = 250;
const styles = StyleSheet.create({
container: {
padding: 8,
},
card: {
flex: 1,
margin: 8,
backgroundColor: '#fff',
borderRadius: 12,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 3,
},
image: {
width: '100%',
height: 150,
borderTopLeftRadius: 12,
borderTopRightRadius: 12,
},
cardContent: {
padding: 12,
},
productName: {
fontSize: 14,
fontWeight: '600',
color: '#1a1a1a',
marginBottom: 4,
},
productPrice: {
fontSize: 16,
fontWeight: '700',
color: '#2563eb',
},
});Flutter Implementation
Flutter's widget-based architecture and Impeller rendering engine deliver consistent 60fps performance. The framework's comprehensive widget library enables pixel-perfect designs across platforms.
// Flutter - Modern Component with Riverpod
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:cached_network_image/cached_network_image.dart';
class Product {
final String id;
final String name;
final double price;
final String image;
Product({required this.id, required this.name, required this.price, required this.image});
}
// Riverpod provider for products
final productsProvider = FutureProvider<List<Product>>((ref) async {
return fetchProducts();
});
class ProductGrid extends ConsumerWidget {
const ProductGrid({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final productsAsync = ref.watch(productsProvider);
final screenWidth = MediaQuery.of(context).size.width;
final crossAxisCount = screenWidth > 600 ? 3 : 2;
return productsAsync.when(
loading: () => ProductGridSkeleton(crossAxisCount: crossAxisCount),
error: (error, stack) => ErrorWidget(error: error),
data: (products) => GridView.builder(
padding: const EdgeInsets.all(8),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: crossAxisCount,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
childAspectRatio: 0.7,
),
itemCount: products.length,
itemBuilder: (context, index) => ProductCard(
product: products[index],
index: index,
),
),
);
}
}
class ProductCard extends StatefulWidget {
final Product product;
final int index;
const ProductCard({super.key, required this.product, required this.index});
@override
State<ProductCard> createState() => _ProductCardState();
}
class _ProductCardState extends State<ProductCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 150),
vsync: this,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => _controller.forward(),
onTapUp: (_) => _controller.reverse(),
onTapCancel: () => _controller.reverse(),
onTap: () => _navigateToDetail(context),
child: ScaleTransition(
scale: _scaleAnimation,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Hero(
tag: 'product-${widget.product.id}',
child: ClipRRect(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(12),
),
child: CachedNetworkImage(
imageUrl: widget.product.image,
fit: BoxFit.cover,
width: double.infinity,
placeholder: (context, url) => Container(
color: Colors.grey[200],
child: const Center(
child: CircularProgressIndicator(),
),
),
errorWidget: (context, url, error) =>
const Icon(Icons.error),
),
),
),
),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.product.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Color(0xFF1a1a1a),
),
),
const Spacer(),
Text(
'\$${widget.product.price.toStringAsFixed(2)}',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Color(0xFF2563eb),
),
),
],
),
),
),
],
),
),
),
);
}
void _navigateToDetail(BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ProductDetailScreen(product: widget.product),
),
);
}
}Performance Comparison
Both frameworks now deliver near-native performance for most applications. The differences are most noticeable in specific scenarios.
| Metric | React Native (New Arch) | Flutter (Impeller) | Native |
|---------------------------|------------------------|-------------------|----------|
| App Startup (cold) | ~1.2s | ~0.9s | ~0.8s |
| List Scrolling (60fps) | 58-60fps | 60fps | 60fps |
| Animation Performance | Excellent | Excellent | Excellent|
| Memory Usage | Higher | Moderate | Lower |
| App Size (minimal) | ~15MB | ~8MB | ~3MB |
| JS/Dart Bundle | ~1.5MB | ~5MB | N/A |
| Complex Animations | Good (Reanimated) | Excellent | Excellent|
| Heavy Computation | Good (JSI) | Good (Isolates) | Excellent|State Management
Both ecosystems have mature state management solutions. React Native benefits from the React ecosystem, while Flutter has purpose-built solutions.
// React Native - Zustand Store with persistence
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface CartItem {
productId: string;
quantity: number;
price: number;
}
interface CartStore {
items: CartItem[];
addItem: (productId: string, price: number) => void;
removeItem: (productId: string) => void;
updateQuantity: (productId: string, quantity: number) => void;
clearCart: () => void;
totalItems: () => number;
totalPrice: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (productId, price) => {
set((state) => {
const existing = state.items.find(i => i.productId === productId);
if (existing) {
return {
items: state.items.map(i =>
i.productId === productId
? { ...i, quantity: i.quantity + 1 }
: i
),
};
}
return {
items: [...state.items, { productId, quantity: 1, price }],
};
});
},
removeItem: (productId) => {
set((state) => ({
items: state.items.filter(i => i.productId !== productId),
}));
},
updateQuantity: (productId, quantity) => {
set((state) => ({
items: state.items.map(i =>
i.productId === productId ? { ...i, quantity } : i
),
}));
},
clearCart: () => set({ items: [] }),
totalItems: () => get().items.reduce((sum, i) => sum + i.quantity, 0),
totalPrice: () =>
get().items.reduce((sum, i) => sum + i.price * i.quantity, 0),
}),
{
name: 'cart-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// Usage in component
function CartButton() {
const totalItems = useCartStore((state) => state.totalItems());
return (
<Pressable style={styles.cartButton}>
<CartIcon />
{totalItems > 0 && (
<View style={styles.badge}>
<Text style={styles.badgeText}>{totalItems}</Text>
</View>
)}
</Pressable>
);
}// Flutter - Riverpod with code generation
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'cart_provider.g.dart';
class CartItem {
final String productId;
final int quantity;
final double price;
CartItem({required this.productId, required this.quantity, required this.price});
CartItem copyWith({int? quantity}) {
return CartItem(
productId: productId,
quantity: quantity ?? this.quantity,
price: price,
);
}
Map<String, dynamic> toJson() => {
'productId': productId,
'quantity': quantity,
'price': price,
};
factory CartItem.fromJson(Map<String, dynamic> json) => CartItem(
productId: json['productId'],
quantity: json['quantity'],
price: json['price'],
);
}
@riverpod
class Cart extends _$Cart {
@override
List<CartItem> build() {
_loadFromStorage();
return [];
}
Future<void> _loadFromStorage() async {
final prefs = await SharedPreferences.getInstance();
final cartJson = prefs.getString('cart');
if (cartJson != null) {
final items = (jsonDecode(cartJson) as List)
.map((e) => CartItem.fromJson(e))
.toList();
state = items;
}
}
Future<void> _saveToStorage() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(
'cart',
jsonEncode(state.map((e) => e.toJson()).toList()),
);
}
void addItem(String productId, double price) {
final existingIndex = state.indexWhere((i) => i.productId == productId);
if (existingIndex != -1) {
state = [
...state.sublist(0, existingIndex),
state[existingIndex].copyWith(
quantity: state[existingIndex].quantity + 1,
),
...state.sublist(existingIndex + 1),
];
} else {
state = [
...state,
CartItem(productId: productId, quantity: 1, price: price),
];
}
_saveToStorage();
}
void removeItem(String productId) {
state = state.where((i) => i.productId != productId).toList();
_saveToStorage();
}
void updateQuantity(String productId, int quantity) {
state = state.map((i) =>
i.productId == productId ? i.copyWith(quantity: quantity) : i
).toList();
_saveToStorage();
}
void clearCart() {
state = [];
_saveToStorage();
}
}
@riverpod
int totalItems(TotalItemsRef ref) {
final cart = ref.watch(cartProvider);
return cart.fold(0, (sum, item) => sum + item.quantity);
}
@riverpod
double totalPrice(TotalPriceRef ref) {
final cart = ref.watch(cartProvider);
return cart.fold(0.0, (sum, item) => sum + item.price * item.quantity);
}
// Usage in widget
class CartButton extends ConsumerWidget {
const CartButton({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final itemCount = ref.watch(totalItemsProvider);
return Stack(
children: [
IconButton(
icon: const Icon(Icons.shopping_cart),
onPressed: () => Navigator.pushNamed(context, '/cart'),
),
if (itemCount > 0)
Positioned(
right: 0,
top: 0,
child: Container(
padding: const EdgeInsets.all(4),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'$itemCount',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
),
],
);
}
}Decision Framework
Choosing Between Flutter and React Native
Choose React Native when:
- Team has JavaScript/TypeScript experience
- Sharing code with React web app
- Need extensive third-party native modules
- Prefer native platform look and feel
- Building content-focused apps
Choose Flutter when:
- Need consistent UI across platforms
- Building highly custom/branded UIs
- Performance is critical (games, animations)
- Starting fresh without JS expertise
- Desktop/web targets are important
Conclusion
Both Flutter and React Native are excellent choices for cross-platform development in 2026. React Native's New Architecture has eliminated most performance concerns, while Flutter's Impeller engine delivers consistently smooth experiences. Your choice should be based on team expertise, existing codebase, and specific project requirements rather than perceived performance differences.
Need help deciding or building your mobile app? Contact Jishu Labs for expert mobile development consulting and implementation.
About Riken Patel
Riken Patel is the Mobile Lead at Jishu Labs with 7+ years of experience building production apps for iOS and Android. He has shipped apps at Ford, Intuitive Surgical, and multiple startups.