• React Core: Creates “stuff” on the page with Javascript. How? VDOM!
  • VDOM (Virtual DOM):
    • In-memory representation of the actual DOM. Like a lightweight, optimized database for the UI.
    • Organized as a tree structure (components/DOM elements as nodes).
    • Reconciliation:
      • React compares the new VDOM with the previous VDOM when data changes (state, props, etc.).
      • Creates “patches” representing minimum changes needed for the real DOM.
      • React uses smart heuristics (batching updates in React 18+, root-based comparisons) to minimize re-rendering time.
    • VDOM Advantages:
      • Sets a reasonable ceiling on rendering performance.
      • Can batch operations to avoid costly re-renders.
    • VDOM Disadvantages:
      • Inherently adds latency compared to direct DOM manipulation (due to comparison and patching).
      • Never faster than directly manipulating the DOM. (Tradeoff: dev experience vs. raw speed)

Props, Re-renders, and the Prop-Drilling Problem

  • Props: How you pass data from parent to child components.

  • Passed via reference (pointers), not value.

    • Good: Avoids data duplication, mutations propagate.
    • Bad: Hard to tell what actually needs to re-render, causing performance issues.
  • Prop Drilling Scenario (A B C D):

    • A holds data x, needs to pass it to D.

    • A passes x to B, B to C, C to D.

    • Only D uses x, but A, B, C, and D all re-render when x changes. Why?!

    • Code Example:

      import React, { useState } from "react"
       
      const ComponentD = ({ x }) => {
        console.log("Component D rendered")
        return <div>Value of x: {x}</div>
      }
       
      const ComponentC = ({ x }) => {
        console.log("Component C rendered")
        return <ComponentD x={x} />
      }
       
      const ComponentB = ({ x }) => {
        console.log("Component B rendered")
        return <ComponentC x={x} />
      }
       
      const ComponentA = () => {
        const [x, setX] = useState(0)
        console.log("Component A rendered")
       
        return (
          <div>
            <button onClick={() => setX(x + 1)}>Increment x</button>
            <ComponentB x={x} />
          </div>
        )
      }
    • Clicking “Increment x” re-renders A, B, C, and D.

    • B and C don’t even use x directly!

    • Reason:

      • VDOM + pass-by-reference.
      • A’s state update creates a new reference for the x prop.
      • VDOM sees the x prop has changed for B and C (different reference).
      • Triggers re-render even if the underlying value of x is the same.
      • This is inherent to JavaScript and React. React follows a top-down approach and will re-render the component which modified the state, as well as all it’s children.
  • Implications of Prop Drilling:

    • In larger apps, complex states passed down the tree can cause a cascade of re-renders.
    • Even unaffected children re-render, creating a performance bottleneck.
    • Hard to debug due to unnecessary re-renders.
    • Trade-off: React prioritizes keeping the UI in sync with the state (even if it means unnecessary re-renders).
    • React’s reconciliation is smart, but not perfect. It errs on the side of caution by re-rendering.

Why Should You Care?

  • Performance does matter, even if people don’t notice small FPS differences.
  • It’s hard to work your way out of performance issues later. Design for performance from the start.
  • Prop drilling makes components too coupled.
  • React should be about composition, pure functions (inputs outputs, no side effects).
  • Prop drilling means a component is concerned with its parent’s and children’s responsibilities.
  • Makes components less composable, less performant, harder to test.

What to Do?