Re-render Control & Optimization Strategies

📖 Concept

Re-renders are NOT inherently bad — they're how React updates the UI. The problem is unnecessary re-renders that trigger expensive reconciliation, bridge calls (old arch), or native view updates.

Understanding the re-render cascade:

Parent re-renders
  → ALL children re-render (even if their props haven't changed)
  → Each child's children re-render
  → ... entire subtree re-renders

This is React's DEFAULT behavior. React.memo, useMemo, and useCallback are the tools to short-circuit this cascade.

The performance measurement hierarchy:

  1. Renders (cheapest) — Component function called, virtual tree produced
  2. Reconciliation — Diffing old vs new tree
  3. Native updates (most expensive) — Crossing JS-Native boundary, updating native views

Optimization strategy (in order of impact):

  1. Fix the architecture — Move state as close to where it's used as possible (colocate state)
  2. Prevent cascading renders — React.memo for expensive subtrees
  3. Stabilize references — useCallback, useMemo for props passed to memoized children
  4. Reduce work per render — useMemo for expensive computations
  5. Virtualize lists — FlatList/FlashList for long lists
  6. Native-driven animations — Reanimated, useNativeDriver
  7. Lazy loading — React.lazy, dynamic import for heavy screens

💻 Code Example

codeTap to expand ⛶
1// === SYSTEMATIC RE-RENDER OPTIMIZATION ===
2
3// 1. PROBLEM: Global state causes cascade re-renders
4// ❌ Single global context — EVERY consumer re-renders on ANY change
5const AppContext = React.createContext({
6 user: null,
7 theme: 'light',
8 notifications: [],
9 cart: [],
10 settings: {},
11});
12
13function App() {
14 const [state, dispatch] = useReducer(appReducer, initialState);
15 // When notifications update, the entire app re-renders
16 // including components that only need theme!
17 return (
18 <AppContext.Provider value={state}>
19 <Navigator />
20 </AppContext.Provider>
21 );
22}
23
24// ✅ FIX: Split contexts by update frequency
25const UserContext = React.createContext(null); // Rarely changes
26const ThemeContext = React.createContext('light'); // Very rarely changes
27const NotificationContext = React.createContext([]); // Changes frequently
28const CartContext = React.createContext([]); // Changes sometimes
29
30// Even better: use Zustand with selectors
31import { create } from 'zustand';
32
33const useAppStore = create((set) => ({
34 user: null,
35 theme: 'light',
36 notifications: [],
37 cart: [],
38 setTheme: (theme) => set({ theme }),
39 addNotification: (n) => set((s) => ({
40 notifications: [...s.notifications, n]
41 })),
42}));
43
44// Components only re-render when THEIR selected slice changes
45function ThemeButton() {
46 const theme = useAppStore(state => state.theme); // Only theme changes trigger re-render
47 return <Button title={theme} />;
48}
49
50function NotificationBadge() {
51 const count = useAppStore(state => state.notifications.length); // Only count matters
52 return <Badge count={count} />;
53}
54
55// 2. PATTERN: Component composition to avoid re-renders
56// ❌ Expensive child re-renders because parent state updates
57function Screen() {
58 const [inputValue, setInputValue] = useState('');
59
60 return (
61 <View>
62 <TextInput value={inputValue} onChangeText={setInputValue} />
63 {/* ExpensiveList re-renders on EVERY keystroke even though
64 it doesn't use inputValue! */}
65 <ExpensiveList items={items} />
66 </View>
67 );
68}
69
70// ✅ FIX: Lift the input into its own component (state colocation)
71function Screen() {
72 return (
73 <View>
74 <SearchInput /> {/* State lives here — only this re-renders */}
75 <ExpensiveList items={items} /> {/* Never re-renders from typing */}
76 </View>
77 );
78}
79
80function SearchInput() {
81 const [inputValue, setInputValue] = useState('');
82 return <TextInput value={inputValue} onChangeText={setInputValue} />;
83}
84
85// 3. PATTERN: Stable callback references for memoized children
86const MemoizedItem = React.memo(function Item({
87 item,
88 onPress,
89 onLongPress
90}) {
91 return (
92 <Pressable onPress={() => onPress(item.id)} onLongPress={() => onLongPress(item.id)}>
93 <Text>{item.title}</Text>
94 </Pressable>
95 );
96});
97
98function ItemList({ items }) {
99 // ✅ Stable references — MemoizedItem won't re-render unless item changes
100 const onPress = useCallback((id) => {
101 navigation.navigate('Detail', { id });
102 }, [navigation]);
103
104 const onLongPress = useCallback((id) => {
105 showActionSheet(id);
106 }, []);
107
108 return items.map(item => (
109 <MemoizedItem key={item.id} item={item} onPress={onPress} onLongPress={onLongPress} />
110 ));
111}

🏋️ Practice Exercise

Re-render Optimization Exercises:

  1. Add console.log('render', componentName) to 10 components and identify unnecessary re-renders during typical usage
  2. Use React DevTools Profiler to visualize the render cascade when state changes
  3. Refactor a global context into split contexts and measure the reduction in re-renders
  4. Replace a Context-based state with Zustand selectors and compare render counts
  5. Apply the "state colocation" pattern to a screen with a search input and a list
  6. Create a before/after benchmark of FlatList performance with and without memoized renderItem

⚠️ Common Mistakes

  • Using React.memo on every component — the comparison overhead can be more costly than the re-render for simple components

  • Passing inline objects/functions as props — <Child style={{ flex: 1 }} /> creates a new object every render, defeating React.memo

  • Using a single context for all app state — every consumer re-renders on every change to any part of the state

  • Optimizing renders before measuring — always profile first to identify actual bottlenecks, don't optimize speculatively

  • Not understanding that useCallback/useMemo don't prevent re-renders of the component using them — they help memoized CHILDREN

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for Re-render Control & Optimization Strategies. Login to unlock this feature.