unknown39825

Advance React Chapter 1

This chapter introduces React re-renders through a practical performance problem scenario. It demonstrates how a simple modal dialog implementation can cause performance issues and teaches the "moving state down" technique as a solution.

Chapter 1: Intro to Re-renders - Summary & Key Points

The Problem Scenario

  • Situation: Adding a simple modal dialog button to a large, performance-sensitive app
  • Issue: The dialog takes almost a second to open
  • Root Cause: State placed at the top level causes unnecessary re-renders of all child components
// Problematic approach - state at App level
const App = () => {
  const [isOpen, setIsOpen] = useState(false); // ❌ Too high up!
  
  return (
    <div className="layout">
      <Button onClick={() => setIsOpen(true)}>Open dialog</Button>
      {isOpen ? <ModalDialog onClose={() => setIsOpen(false)} /> : null}
      <VerySlowComponent />     // ❌ Re-renders unnecessarily
      <BunchOfStuff />          // ❌ Re-renders unnecessarily  
      <OtherStuffAlsoComplicated /> // ❌ Re-renders unnecessarily
    </div>
  );
};

Core Concepts

React Component Lifecycle Stages

  1. Mounting: Component appears on screen for the first time
  2. Unmounting: Component is removed and cleaned up
  3. Re-rendering: Component updates with new information (lightweight compared to mounting)

How Re-renders Work

  • Initial Source: State updates are the root cause of all re-renders
  • Propagation: React re-renders ALL nested components down the tree from where state changed
  • Direction: React never goes "up" the render tree - only downward
  • Chain Reaction: State update → Component re-renders → All children re-render → Their children re-render, etc.

The Big Re-renders Myth 🚨

Common Misconception: "Components re-render when props change"
Reality: Components re-render when:

  • A state update is triggered in a parent component
  • Props changes alone do NOT trigger re-renders
  • React doesn't monitor prop changes unless using React.memo
// This WON'T work - no state update triggered
const App = () => {
  let isOpen = false; // ❌ Just a variable, not state
  
  return (
    <Button onClick={() => (isOpen = true)}> // ❌ Won't trigger re-render
      Open dialog
    </Button>
  );
};

The Solution: Moving State Down

Problem Analysis

The modal state was only used by:

  • The button (to open dialog)
  • The modal dialog itself

All other components didn't need this state but were re-rendering anyway.

Solution Implementation

// 1. Extract components that need the state
const ButtonWithModalDialog = () => {
  const [isOpen, setIsOpen] = useState(false); // ✅ State moved down
  
  return (
    <>
      <Button onClick={() => setIsOpen(true)}>Open dialog</Button>
      {isOpen ? <ModalDialog onClose={() => setIsOpen(false)} /> : null}
    </>
  );
};

// 2. Use in main app without state
const App = () => {
  return (
    <div className="layout">
      <ButtonWithModalDialog /> {/* ✅ Isolated state */}
      <VerySlowComponent />     {/* ✅ No longer re-renders */}
      <BunchOfStuff />          {/* ✅ No longer re-renders */}
      <OtherStuffAlsoComplicated /> {/* ✅ No longer re-renders */}
    </div>
  );
};

Result

  • Modal opens instantly
  • Only the button and dialog re-render when needed
  • Rest of the app remains unaffected

The Danger of Custom Hooks ⚠️

Hidden State Problem

Custom hooks can hide the fact that state exists, but the performance impact remains:

const useModalDialog = () => {
  const [isOpen, setIsOpen] = useState(false); // State still exists!
  // ... other logic
};

const App = () => {
  const { isOpen, open, close } = useModalDialog(); // ❌ App still re-renders
  // ... rest of component
};

Chain Reaction Effect

State updates deep in hook chains still trigger re-renders:

const useResizeDetector = () => {
  const [width, setWidth] = useState(0); // State update on resize
  // ... resize logic
};

const useModalDialog = () => {
  useResizeDetector(); // Uses hook with state
  // ... modal logic
};

const App = () => {
  const dialog = useModalDialog(); // ❌ Re-renders on every window resize!
  // ...
};

Hook Solution

Still need to move the hook usage down to a smaller component:

const ButtonWithModalDialog = () => {
  const { isOpen, open, close } = useModalDialog(); // ✅ Isolated
  return (
    <>
      <Button onClick={open}>Open dialog</Button>
      {isOpen ? <ModalDialog onClose={close} /> : null}
    </>
  );
};

Key Takeaways

  1. Re-rendering is essential - It's how React updates components with new data and enables interactivity

  2. State updates trigger re-renders - Every re-render starts with a state change

  3. Re-renders cascade downward - When a component re-renders, ALL nested components re-render too

  4. Props changes don't trigger re-renders - Only state updates do (unless using React.memo)

  5. "Moving state down" prevents unnecessary re-renders - Place state as close as possible to where it's actually used

  6. Custom hooks don't isolate state - State in hooks still affects the component using the hook

  7. Hook chains propagate re-renders - Any state update in a chain of hooks will trigger re-renders in the consuming component

  8. Isolate state in small, light components - This prevents performance problems and keeps re-renders minimal

Performance Principle

"Where you put state is very important. Ideally, to avoid future performance problems, you'd want to isolate it as much as possible to as tiny and light components as possible."

What's Next

The next chapter will explore another pattern for preventing unnecessary re-renders: using elements, children as props, and component composition techniques.