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 thecontent
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
- Function Call: React calls the component function (this is the "re-render")
- Tree Building: React builds a tree of Element objects from the function's return
- Comparison: React compares the "before" and "after" trees
- 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
- Same reference (
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
- User scrolls →
onScroll
fires →setPosition
called ScrollableWithMovingBlock
re-renders- 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
- Component: A function that returns Elements (
const A = () => <B />
) - Element: An object describing what to render (
const b = <B />
) - Re-render: React calling the component function
- Reconciliation: React comparing Element trees using
Object.is()
Performance Patterns
- Components as Props: Elements passed as props won't re-render when the receiving component's state changes
- Children are Props:
children
behave exactly like any other prop - 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.