Memoization Strategies & Error Boundaries
📖 Concept
Memoization in React is the technique of caching computation results to avoid redundant work. But it's not free — memoization has costs (memory for cached values, comparison overhead for deps). The key is knowing WHEN it helps.
When to memoize (and when NOT to):
| Scenario | Memoize? | Why |
|---|---|---|
| Pure display component with primitive props | ❌ No | Re-render is cheaper than comparison |
| Component receiving new object props every render | ⚠️ Fix the parent | Memoizing here is a bandaid |
| Expensive computation (sorting 10K items) | ✅ useMemo | Computation cost > comparison cost |
| Callback passed to memoized child | ✅ useCallback | Prevents child re-render |
| Context consumer rendering large subtree | ✅ memo + useMemo | Context changes re-render all consumers |
| FlatList renderItem | ✅ memo | Prevents re-render of off-screen items |
React.memo deep dive:
- Wraps a component to skip re-render if props haven't changed
- Default: shallow comparison (Object.is on each prop)
- Custom:
React.memo(Component, (prevProps, nextProps) => areEqual) - Does NOT prevent re-render from internal state changes or context changes
- Works only for props coming from the parent
Error Boundaries: Error boundaries catch JavaScript errors in their child component tree, log them, and display a fallback UI. They are CLASS components (no hook equivalent yet) that implement:
static getDerivedStateFromError(error)— render fallback UIcomponentDidCatch(error, errorInfo)— log error (to Sentry, etc.)
What Error Boundaries catch: ✅ Rendering errors, lifecycle methods, constructors of child tree ❌ Event handlers, async code, errors in the boundary itself, SSR
In React Native production apps, error boundaries are critical. Without them, a crash in one component takes down the entire app. With them, you can isolate failures to individual features.
💻 Code Example
1// === MEMOIZATION STRATEGY GUIDE ===23// 1. React.memo — prevent unnecessary re-renders4const ExpensiveListItem = React.memo(function ListItem({5 item,6 onPress7}: {8 item: Product;9 onPress: (id: string) => void;10}) {11 // Only re-renders if item or onPress reference changes12 return (13 <Pressable onPress={() => onPress(item.id)}>14 <Text>{item.name}</Text>15 <Text>{formatPrice(item.price)}</Text>16 </Pressable>17 );18});1920// 2. The PARENT must cooperate — stabilize the callback21function ProductList({ products }: { products: Product[] }) {22 // ❌ Without useCallback, onPress is a new function every render23 // → ExpensiveListItem.memo is useless24 // const onPress = (id: string) => navigate('Product', { id });2526 // ✅ With useCallback, reference is stable27 const onPress = useCallback((id: string) => {28 navigate('Product', { id });29 }, [navigate]);3031 const renderItem = useCallback(({ item }: { item: Product }) => (32 <ExpensiveListItem item={item} onPress={onPress} />33 ), [onPress]);3435 return <FlatList data={products} renderItem={renderItem} />;36}3738// 3. useMemo for expensive computations39function AnalyticsDashboard({ transactions }: { transactions: Transaction[] }) {40 // Only recompute when transactions array reference changes41 const stats = useMemo(() => ({42 total: transactions.reduce((sum, t) => sum + t.amount, 0),43 average: transactions.reduce((sum, t) => sum + t.amount, 0) / transactions.length,44 byCategory: groupBy(transactions, 'category'),45 topMerchants: getTopN(transactions, 'merchant', 10),46 // Any of these could be O(n) or O(n log n) — worth memoizing47 }), [transactions]);4849 return <StatsView stats={stats} />;50}5152// 4. Context + Memoization to prevent cascade re-renders53const ThemeContext = React.createContext<Theme>(defaultTheme);5455function ThemeProvider({ children }: { children: React.ReactNode }) {56 const [theme, setTheme] = useState<Theme>(defaultTheme);5758 // ✅ Memoize the context value to prevent re-renders59 // when ThemeProvider re-renders for OTHER reasons60 const value = useMemo(() => ({ theme, setTheme }), [theme]);6162 return (63 <ThemeContext.Provider value={value}>64 {children}65 </ThemeContext.Provider>66 );67}6869// === ERROR BOUNDARIES ===7071// 5. Production Error Boundary with crash reporting72class ErrorBoundary extends React.Component<73 { fallback: React.ReactNode; children: React.ReactNode; onError?: (error: Error) => void },74 { hasError: boolean; error: Error | null }75> {76 state = { hasError: false, error: null };7778 static getDerivedStateFromError(error: Error) {79 return { hasError: true, error };80 }8182 componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {83 // Report to crash analytics (Sentry, Crashlytics)84 crashlytics().recordError(error, {85 componentStack: errorInfo.componentStack,86 });87 this.props.onError?.(error);88 }8990 render() {91 if (this.state.hasError) {92 return this.props.fallback;93 }94 return this.props.children;95 }96}9798// 6. Granular error boundaries — isolate feature failures99function AppLayout() {100 return (101 <View style={styles.container}>102 <ErrorBoundary fallback={<HeaderFallback />}>103 <Header />104 </ErrorBoundary>105106 <ErrorBoundary fallback={<FeedErrorState />}>107 <MainFeed /> {/* If feed crashes, header and tabs still work */}108 </ErrorBoundary>109110 <ErrorBoundary fallback={<TabBarFallback />}>111 <TabBar />112 </ErrorBoundary>113 </View>114 );115}
🏋️ Practice Exercise
Memoization & Error Boundary Exercises:
- Profile a FlatList with 1000 items — add React.memo to renderItem and measure the difference
- Create a component that proves useMemo prevents recomputation — log inside the memo callback
- Build a custom error boundary that shows a "Retry" button and resets on press
- Implement a
useWhyDidYouRenderhook that logs exactly which deps changed to cause a re-render - Create a context provider that splits frequently-changing and rarely-changing values to minimize re-renders
- Benchmark: compare render times of a memoized vs non-memoized component with 50 props
⚠️ Common Mistakes
Wrapping every component in React.memo — adds comparison overhead; only useful for costly re-renders or stable prop patterns
Using useMemo for simple calculations —
useMemo(() => a + b, [a, b])is SLOWER than justa + bdue to memoization overheadPassing new object/array literals as props to a memoized child —
<Memo style={{ flex: 1 }} />defeats memo because the style object is new every renderNot realizing Error Boundaries only catch errors in the REACT TREE (render, lifecycle) — not in event handlers, async code, or setTimeout
Creating a single top-level error boundary — use granular boundaries so one feature crash doesn't take down the whole app
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Memoization Strategies & Error Boundaries. Login to unlock this feature.