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 UI
  • componentDidCatch(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

codeTap to expand ⛶
1// === MEMOIZATION STRATEGY GUIDE ===
2
3// 1. React.memo — prevent unnecessary re-renders
4const ExpensiveListItem = React.memo(function ListItem({
5 item,
6 onPress
7}: {
8 item: Product;
9 onPress: (id: string) => void;
10}) {
11 // Only re-renders if item or onPress reference changes
12 return (
13 <Pressable onPress={() => onPress(item.id)}>
14 <Text>{item.name}</Text>
15 <Text>{formatPrice(item.price)}</Text>
16 </Pressable>
17 );
18});
19
20// 2. The PARENT must cooperate — stabilize the callback
21function ProductList({ products }: { products: Product[] }) {
22 // ❌ Without useCallback, onPress is a new function every render
23 // → ExpensiveListItem.memo is useless
24 // const onPress = (id: string) => navigate('Product', { id });
25
26 // ✅ With useCallback, reference is stable
27 const onPress = useCallback((id: string) => {
28 navigate('Product', { id });
29 }, [navigate]);
30
31 const renderItem = useCallback(({ item }: { item: Product }) => (
32 <ExpensiveListItem item={item} onPress={onPress} />
33 ), [onPress]);
34
35 return <FlatList data={products} renderItem={renderItem} />;
36}
37
38// 3. useMemo for expensive computations
39function AnalyticsDashboard({ transactions }: { transactions: Transaction[] }) {
40 // Only recompute when transactions array reference changes
41 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 memoizing
47 }), [transactions]);
48
49 return <StatsView stats={stats} />;
50}
51
52// 4. Context + Memoization to prevent cascade re-renders
53const ThemeContext = React.createContext<Theme>(defaultTheme);
54
55function ThemeProvider({ children }: { children: React.ReactNode }) {
56 const [theme, setTheme] = useState<Theme>(defaultTheme);
57
58 // ✅ Memoize the context value to prevent re-renders
59 // when ThemeProvider re-renders for OTHER reasons
60 const value = useMemo(() => ({ theme, setTheme }), [theme]);
61
62 return (
63 <ThemeContext.Provider value={value}>
64 {children}
65 </ThemeContext.Provider>
66 );
67}
68
69// === ERROR BOUNDARIES ===
70
71// 5. Production Error Boundary with crash reporting
72class 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 };
77
78 static getDerivedStateFromError(error: Error) {
79 return { hasError: true, error };
80 }
81
82 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 }
89
90 render() {
91 if (this.state.hasError) {
92 return this.props.fallback;
93 }
94 return this.props.children;
95 }
96}
97
98// 6. Granular error boundaries — isolate feature failures
99function AppLayout() {
100 return (
101 <View style={styles.container}>
102 <ErrorBoundary fallback={<HeaderFallback />}>
103 <Header />
104 </ErrorBoundary>
105
106 <ErrorBoundary fallback={<FeedErrorState />}>
107 <MainFeed /> {/* If feed crashes, header and tabs still work */}
108 </ErrorBoundary>
109
110 <ErrorBoundary fallback={<TabBarFallback />}>
111 <TabBar />
112 </ErrorBoundary>
113 </View>
114 );
115}

🏋️ Practice Exercise

Memoization & Error Boundary Exercises:

  1. Profile a FlatList with 1000 items — add React.memo to renderItem and measure the difference
  2. Create a component that proves useMemo prevents recomputation — log inside the memo callback
  3. Build a custom error boundary that shows a "Retry" button and resets on press
  4. Implement a useWhyDidYouRender hook that logs exactly which deps changed to cause a re-render
  5. Create a context provider that splits frequently-changing and rarely-changing values to minimize re-renders
  6. 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 just a + b due to memoization overhead

  • Passing new object/array literals as props to a memoized child — <Memo style={{ flex: 1 }} /> defeats memo because the style object is new every render

  • Not 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.