Elements & Children Props
Published on
4 min read • --- views
Welcome back, fellow React enthusiasts!
Previously, we discussed re-renders and the "moving state down" pattern. That technique works great when you can isolate stateful logic into a leaf component. But sometimes the architecture doesn't allow for that.
What do you do when state must live at the top, yet you don't want to tank performance?
Let's explore a real scenario. You're building a dashboard with a resizable sidebar. The sidebar width is controlled by dragging a handle, and the entire content area needs to respond to this width change.
The Challenge
Here's what you need:
- A draggable divider that updates the sidebar width in real-time.
- The sidebar wraps around
ExpensiveChart,DataGrid, andAnalyticsPanel. - As you drag, the layout adjusts smoothly.
The naive approach puts the drag state at the top level (or perhaps tries to hide it in a custom hook, which doesn't change anything):
const Dashboard = () => { const [sidebarWidth, setSidebarWidth] = useState(300); const handleDrag = (e) => { setSidebarWidth(e.clientX); }; return ( <div className="dashboard-layout"> <div className="sidebar" style={{ width: sidebarWidth }}> <DragHandle onDrag={handleDrag} /> {/* These re-render constantly while dragging! */} <ExpensiveChart /> <DataGrid /> <AnalyticsPanel /> </div> <MainContent /> </div> ); };
This implementation will stutter badly. Every mouse movement fires a state update, causing Dashboard to re-render.
When Dashboard re-renders, so does everything nested inside it. The "moving state down" approach fails here because
the sidebar div needs to wrap the expensive components while also knowing about the width.
You might reach for React.memo, but there's a cleaner compositional approach that leverages how React handles
elements.
The Fix: Composition with Children
Extract the resize logic into a dedicated component that accepts its contents as children:
const ResizableSidebar = ({ children }) => { const [width, setWidth] = useState(300); const handleDrag = (e) => { setWidth(e.clientX); }; return ( <div className="sidebar" style={{ width }}> <DragHandle onDrag={handleDrag} /> {children} </div> ); };
Refactor Dashboard to use this wrapper:
const Dashboard = () => { return ( <div className="dashboard-layout"> <ResizableSidebar> {/* Passed as props, not defined here */} <ExpensiveChart /> <DataGrid /> <AnalyticsPanel /> </ResizableSidebar> <MainContent /> </div> ); };
Now dragging is fluid. The expensive components stay untouched even though they visually live inside a component that updates dozens of times per second.
The Mechanics Behind It
This behavior stems from the distinction between Components and Elements.
A Component is the function itself (Dashboard, ResizableSidebar).
An Element is the object produced when JSX executes ({ type: ExpensiveChart, props: {...} }).
Here's what happens step by step:
Dashboardrenders once and creates Element objects for<ExpensiveChart />,<DataGrid />, and<AnalyticsPanel />.- These Element objects get passed into
ResizableSidebarthrough thechildrenprop. - User starts dragging.
ResizableSidebarupdates itswidthstate repeatedly. ResizableSidebarre-executes, returning a new sidebar div with the updated width.- React checks the
childrenprop. Has the reference changed? - No.
Dashboardnever re-rendered, sochildrenpoints to the exact same objects in memory. - React skips reconciling that entire subtree.
The key insight: React compares element references, not their visual position in the tree.
Understanding the JSX Transformation
Remember that children is an ordinary prop. The nested syntax is syntactic sugar.
Elements are expressions, so these two snippets produce identical results:
<Wrapper> <Content /> </Wrapper>
<Wrapper children={<Content />} />
You could use any prop name: content, slot, body. The optimization works as long as the Element is created in a
scope that doesn't re-render and then passed to the stateful component.
Wrapping Up
The previous article showed how pushing state into a child component prevents unnecessary re-renders. This article demonstrated the inverse: lifting static UI out of the stateful component by passing it as props.
Both patterns serve the same purpose: decoupling what changes from what stays stable.
You might also like:
Custom Hooks Pitfalls
--- views
Learn about performance risks in React custom hooks, how they cause unexpected re-renders, and strategies to manage state efficiently.
React Re-Renders
--- views
Dive into the mechanics of React re-renders — learn what causes them, how they impact performance, and how to manage them effectively.
How to create a grid with flexbox in React
--- views
Learn how to build a reusable flexbox-based Grid component in React using BEM methodology and clsx for clean class management.
Share it: