Why Performance Optimization Matters
Now that Rork makes it easy to build mobile apps quickly, the next step is polishing the user experience. Even a one-second delay in app loading can significantly increase user drop-off rates. This article covers concrete techniques for improving Rork app performance across three areas: rendering, memory, and networking.
Rendering Optimization
Preventing Unnecessary Re-renders
In React Native-based Rork apps, component re-rendering is often the biggest performance bottleneck. Using React.memo and useMemo properly eliminates unnecessary redraws.
// components/ProductCard.tsx
// Improve list rendering performance with memoization
import React, { useMemo } from "react";
import { View, Text, Image, StyleSheet } from "react-native";
interface Product {
id: string;
name: string;
price: number;
imageUrl: string;
discount?: number;
}
// React.memo prevents re-rendering when props haven't changed
const ProductCard = React.memo(({ product }: { product: Product }) => {
// Memoize price calculation (only recomputes when price/discount change)
const finalPrice = useMemo(() => {
if (product.discount) {
return Math.round(product.price * (1 - product.discount / 100));
}
return product.price;
}, [product.price, product.discount]);
return (
<View style={styles.card}>
<Image source={{ uri: product.imageUrl }} style={styles.image} />
<Text style={styles.name}>{product.name}</Text>
<Text style={styles.price}>${finalPrice.toFixed(2)}</Text>
</View>
);
});
const styles = StyleSheet.create({
card: { padding: 12, borderRadius: 8, backgroundColor: "#fff", marginBottom: 8 },
image: { width: "100%", height: 160, borderRadius: 6 },
name: { fontSize: 16, fontWeight: "600", marginTop: 8 },
price: { fontSize: 14, color: "#e74c3c", marginTop: 4 },
});
export default ProductCard;
// Usage: render many cards efficiently with FlatList
// <FlatList data={products} renderItem={({item}) => <ProductCard product={item} />} />Tuning FlatList Performance
When displaying large datasets, FlatList configuration is critical.
// screens/ProductListScreen.tsx
// Optimized FlatList configuration
import { FlatList } from "react-native";
import ProductCard from "../components/ProductCard";
export function ProductListScreen({ products }: { products: Product[] }) {
return (
<FlatList
data={products}
renderItem={({ item }) => <ProductCard product={item} />}
keyExtractor={(item) => item.id}
removeClippedSubviews={true} // Unmount off-screen components
maxToRenderPerBatch={10} // Max items per render batch
windowSize={5} // Render window multiplier (default 21 → 5)
initialNumToRender={8} // Initial render count
getItemLayout={(_, index) => ({ // Skip layout calculation for fixed-height items
length: 200,
offset: 200 * index,
index,
})}
/>
);
}
// Expected improvement: ~60% faster initial render for 1000-item listsMemory Management
Image Memory Optimization
Images consume the most memory in mobile apps. In Rork apps, proper caching strategies and size specifications can dramatically reduce memory usage.
// utils/imageOptimizer.ts
// Image resizing and cache management
interface ImageConfig {
uri: string;
width: number;
height: number;
quality?: number; // 1-100
}
function getOptimizedImageUrl(config: ImageConfig): string {
const { uri, width, height, quality = 80 } = config;
// Leverage CDN image transformation (Cloudflare Images example)
if (uri.includes("imagedelivery.net")) {
return `${uri}/w=${width},h=${height},q=${quality},fit=cover`;
}
// imgix example
if (uri.includes("imgix.net")) {
return `${uri}?w=${width}&h=${height}&q=${quality}&auto=format`;
}
return uri;
}
// Usage
const thumbnailUrl = getOptimizedImageUrl({
uri: "https://imagedelivery.net/abc123/product-photo",
width: 300,
height: 200,
quality: 75,
});
console.log(thumbnailUrl);
// Expected: https://imagedelivery.net/abc123/product-photo/w=300,h=200,q=75,fit=coverCleanup in useEffect
The leading cause of memory leaks is subscriptions and timers that aren't cleaned up on unmount.
// hooks/useAutoRefresh.ts
// Proper cleanup prevents memory leaks
import { useEffect, useRef } from "react";
export function useAutoRefresh(callback: () => void, intervalMs: number) {
const callbackRef = useRef(callback);
callbackRef.current = callback;
useEffect(() => {
const timer = setInterval(() => {
callbackRef.current();
}, intervalMs);
// Timer is guaranteed to be cleared when component unmounts
return () => {
clearInterval(timer);
};
}, [intervalMs]);
}
// Usage:
// useAutoRefresh(() => fetchLatestData(), 30000); // Auto-refresh every 30s
// Timer is automatically cleared when the component is destroyedNetwork Efficiency
Debouncing and Caching Requests
For features like search that trigger frequent requests, combining debounce with caching is highly effective.
// hooks/useDebouncedSearch.ts
// Debounce + cache for optimized search performance
import { useState, useEffect, useRef } from "react";
interface SearchResult {
id: string;
title: string;
}
const searchCache = new Map<string, { data: SearchResult[]; timestamp: number }>();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
export function useDebouncedSearch(query: string, delayMs = 300) {
const [results, setResults] = useState<SearchResult[]>([]);
const [loading, setLoading] = useState(false);
const abortRef = useRef<AbortController | null>(null);
useEffect(() => {
if (query.length < 2) {
setResults([]);
return;
}
// Check cache first
const cached = searchCache.get(query);
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
setResults(cached.data);
return;
}
const timer = setTimeout(async () => {
abortRef.current?.abort();
abortRef.current = new AbortController();
setLoading(true);
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: abortRef.current.signal,
});
const data: SearchResult[] = await res.json();
searchCache.set(query, { data, timestamp: Date.now() });
setResults(data);
} catch (error) {
if (error instanceof Error && error.name !== "AbortError") {
console.error("Search failed:", error.message);
}
} finally {
setLoading(false);
}
}, delayMs);
return () => clearTimeout(timer);
}, [query, delayMs]);
return { results, loading };
}
// Usage:
// const { results, loading } = useDebouncedSearch(searchText);
// Behavior: 300ms debounce + 5-min cache eliminates duplicate requestsPerformance Measurement
To verify your optimizations are working, build measurement into your app.
// utils/performanceMonitor.ts
// Simple performance measurement utility
class PerformanceMonitor {
private marks = new Map<string, number>();
start(label: string) {
this.marks.set(label, performance.now());
}
end(label: string): number {
const start = this.marks.get(label);
if (!start) return -1;
const elapsed = Math.round(performance.now() - start);
this.marks.delete(label);
console.log(`[Perf] ${label}: ${elapsed}ms`);
return elapsed;
}
async measure<T>(label: string, fn: () => Promise<T>): Promise<T> {
this.start(label);
const result = await fn();
this.end(label);
return result;
}
}
export const perf = new PerformanceMonitor();
// Usage:
// const data = await perf.measure("fetchProducts", () => api.getProducts());
// Expected output: [Perf] fetchProducts: 245msLooking back
Rork app performance optimization rests on three pillars: rendering (React.memo / FlatList tuning), memory (image optimization / cleanup), and networking (debounce / caching). You don't need to implement everything at once — start with the highest-impact area based on your measurements.
For related reading, check out Rork Basics Guide and Rork AI Features.