unknown39825

Advance React Chapter 2

This chapter explores advanced patterns for preventing re-renders when state can't be easily moved down. It introduces the concepts of Components vs Elements, React's reconciliation process, and the powerful "children as props" pattern for performance optimization.

Chapter 2: Elements, Children as Props, and Re-renders - Summary & Key Points

The Problem Scenario

  • Situation: Building a scrollable area with a floating navigation block that moves based on scroll position
  • Challenge: State (scroll position) needs to be at the wrapper level, but causes all child components to re-render
  • Issue: Can't easily extract state to a smaller component because the scroll handler is on the wrapper div
// Problematic approach - state causes all children to re-render
const MainScrollableArea = () => {
  const [position, setPosition] = useState(300);
  
  const onScroll = (e) => {
    const calculated = getPosition(e.target.scrollTop);
    setPosition(calculated); // ❌ Triggers re-render of ALL children
  };

  return (
    <div className="scrollable-block" onScroll={onScroll}>
      <MovingBlock position={position} />
      <VerySlowComponent />         // ❌ Re-renders on every scroll
      <BunchOfStuff />              // ❌ Re-renders on every scroll  
      <OtherStuffAlsoComplicated /> // ❌ Re-renders on every scroll
    </div>
  );
};

The Solution: Components as Props

Step 1: Extract State Management

Create a component that only handles the stateful logic:

const ScrollableWithMovingBlock = ({ content }) => {
  const [position, setPosition] = useState(300);
  
  const onScroll = (e) => {
    const calculated = getPosition(e.target.scrollTop);
    setPosition(calculated);
  };

  return (
    <div className="scrollable-block" onScroll={onScroll}>
      <MovingBlock position={position} />
      {content} {/* ✅ Passed as prop - won't re-render */}
    </div>
  );
};

Step 2: Pass Components as Props

const App = () => {
  const slowComponents = (
    <>
      <VerySlowComponent />
      <BunchOfStuff />
      <OtherStuffAlsoComplicated />
    </>
  );

  return (
    <ScrollableWithMovingBlock content={slowComponents} />
  );
};

Why This Works

  • Slow components are created in the parent scope (App)
  • When ScrollableWithMovingBlock re-renders, React compares the content prop
  • Since content is created outside the re-rendering component, the object reference stays the same
  • React skips re-rendering the components passed as props

Core Concepts Deep Dive

Components vs Elements

What is a Component?

const Parent = () => {
  return <Child />; // This function is a Component
};
  • A Component is just a function that accepts props and returns Elements
  • Components define what should be rendered

What is an Element?

const element = <Child />; // This is an Element
  • An Element is an object that describes what needs to be rendered
  • Elements are created using JSX syntax (which is sugar for React.createElement)
  • Element object structure:
{
  type: Child,        // Component reference or string for DOM elements
  props: {},          // Props passed to the component
  // ... other React internals
}

React's Re-render Process

The Reconciliation Algorithm

  1. Function Call: React calls the component function (this is the "re-render")
  2. Tree Building: React builds a tree of Element objects from the function's return
  3. Comparison: React compares the "before" and "after" trees
  4. Decision Making: For each Element, React uses Object.is() to compare references
    • Same reference (Object.is() === true): Skip re-rendering this component
    • Different reference (Object.is() === false): Re-render this component

Why Props-as-Components Work

// Component that creates Elements locally
const Parent = () => {
  const [state, setState] = useState();
  return <Child />; // ❌ New object created on each render
};

// Component that receives Elements as props  
const Parent = ({ child }) => {
  const [state, setState] = useState();
  return child; // ✅ Same object reference from parent scope
};

When Parent re-renders:

  • Local Elements: Created fresh each time → Different object reference → Child re-renders
  • Props Elements: Created in parent scope → Same object reference → Child skips re-render

The Children as Props Pattern

The Problem with Custom Props

Passing entire page content through custom props feels awkward:

// Feels weird and unnatural
<ScrollableWithMovingBlock content={slowComponents} />

The Solution: Children Prop

React provides special syntax for the children prop:

// These are equivalent:
<Parent children={<Child />} />

<Parent>
  <Child />
</Parent>

Implementation

// Before: Custom prop
const ScrollableWithMovingBlock = ({ content }) => {
  // ... state logic
  return (
    <div className="scrollable-block" onScroll={onScroll}>
      <MovingBlock position={position} />
      {content}
    </div>
  );
};

// After: Children prop
const ScrollableWithMovingBlock = ({ children }) => {
  // ... same state logic
  return (
    <div className="scrollable-block" onScroll={onScroll}>
      <MovingBlock position={position} />
      {children}
    </div>
  );
};

Usage

// Much more natural and readable
const App = () => {
  return (
    <ScrollableWithMovingBlock>
      <VerySlowComponent />
      <BunchOfStuff />
      <OtherStuffAlsoComplicated />
    </ScrollableWithMovingBlock>
  );
};

Performance Benefits

What Happens During Scroll

  1. User scrolls → onScroll fires → setPosition called
  2. ScrollableWithMovingBlock re-renders
  3. React compares all returned Elements:
    • <MovingBlock />: Created locally → New object → Re-renders ✅ (needs new position)
    • {children}: Passed as prop → Same object → Skips re-render ✅ (performance win)

Result

  • Smooth, lag-free scrolling
  • Only the moving block updates on scroll
  • Heavy components remain untouched

Key Takeaways

Fundamental Concepts

  1. Component: A function that returns Elements (const A = () => <B />)
  2. Element: An object describing what to render (const b = <B />)
  3. Re-render: React calling the component function
  4. Reconciliation: React comparing Element trees using Object.is()

Performance Patterns

  1. Components as Props: Elements passed as props won't re-render when the receiving component's state changes
  2. Children are Props: children behave exactly like any other prop
  3. JSX Nesting Syntax: <Parent><Child /></Parent> is sugar for <Parent children={<Child />} />

When to Use This Pattern

  • When you can't move state down to a smaller component
  • When state needs to be at a wrapper/container level
  • When you have expensive child components that don't need the state
  • When building reusable container components

Pattern Benefits

  • Performance: Prevents unnecessary re-renders
  • Composition: More flexible and reusable components
  • Readability: Natural JSX nesting syntax
  • Maintainability: Clear separation of concerns

What's Next

The next chapter will explore how components as props can be useful beyond just performance optimization, diving into other composition patterns and use cases.