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:
- Renders (cheapest) — Component function called, virtual tree produced
- Reconciliation — Diffing old vs new tree
- Native updates (most expensive) — Crossing JS-Native boundary, updating native views
Optimization strategy (in order of impact):
- Fix the architecture — Move state as close to where it's used as possible (colocate state)
- Prevent cascading renders — React.memo for expensive subtrees
- Stabilize references — useCallback, useMemo for props passed to memoized children
- Reduce work per render — useMemo for expensive computations
- Virtualize lists — FlatList/FlashList for long lists
- Native-driven animations — Reanimated, useNativeDriver
- Lazy loading — React.lazy, dynamic import for heavy screens
💻 Code Example
1// === SYSTEMATIC RE-RENDER OPTIMIZATION ===23// 1. PROBLEM: Global state causes cascade re-renders4// ❌ Single global context — EVERY consumer re-renders on ANY change5const AppContext = React.createContext({6 user: null,7 theme: 'light',8 notifications: [],9 cart: [],10 settings: {},11});1213function App() {14 const [state, dispatch] = useReducer(appReducer, initialState);15 // When notifications update, the entire app re-renders16 // including components that only need theme!17 return (18 <AppContext.Provider value={state}>19 <Navigator />20 </AppContext.Provider>21 );22}2324// ✅ FIX: Split contexts by update frequency25const UserContext = React.createContext(null); // Rarely changes26const ThemeContext = React.createContext('light'); // Very rarely changes27const NotificationContext = React.createContext([]); // Changes frequently28const CartContext = React.createContext([]); // Changes sometimes2930// Even better: use Zustand with selectors31import { create } from 'zustand';3233const 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}));4344// Components only re-render when THEIR selected slice changes45function ThemeButton() {46 const theme = useAppStore(state => state.theme); // Only theme changes trigger re-render47 return <Button title={theme} />;48}4950function NotificationBadge() {51 const count = useAppStore(state => state.notifications.length); // Only count matters52 return <Badge count={count} />;53}5455// 2. PATTERN: Component composition to avoid re-renders56// ❌ Expensive child re-renders because parent state updates57function Screen() {58 const [inputValue, setInputValue] = useState('');5960 return (61 <View>62 <TextInput value={inputValue} onChangeText={setInputValue} />63 {/* ExpensiveList re-renders on EVERY keystroke even though64 it doesn't use inputValue! */}65 <ExpensiveList items={items} />66 </View>67 );68}6970// ✅ 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}7980function SearchInput() {81 const [inputValue, setInputValue] = useState('');82 return <TextInput value={inputValue} onChangeText={setInputValue} />;83}8485// 3. PATTERN: Stable callback references for memoized children86const MemoizedItem = React.memo(function Item({87 item,88 onPress,89 onLongPress90}) {91 return (92 <Pressable onPress={() => onPress(item.id)} onLongPress={() => onLongPress(item.id)}>93 <Text>{item.title}</Text>94 </Pressable>95 );96});9798function ItemList({ items }) {99 // ✅ Stable references — MemoizedItem won't re-render unless item changes100 const onPress = useCallback((id) => {101 navigation.navigate('Detail', { id });102 }, [navigation]);103104 const onLongPress = useCallback((id) => {105 showActionSheet(id);106 }, []);107108 return items.map(item => (109 <MemoizedItem key={item.id} item={item} onPress={onPress} onLongPress={onLongPress} />110 ));111}
🏋️ Practice Exercise
Re-render Optimization Exercises:
- Add
console.log('render', componentName)to 10 components and identify unnecessary re-renders during typical usage - Use React DevTools Profiler to visualize the render cascade when state changes
- Refactor a global context into split contexts and measure the reduction in re-renders
- Replace a Context-based state with Zustand selectors and compare render counts
- Apply the "state colocation" pattern to a screen with a search input and a list
- 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.memoUsing 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.