cloneElement creates a new React element using an existing element as a starting point, allowing you to override props.
Basic Usage
tsximport { cloneElement } from 'react'; const originalElement = <Button color="blue">Click</Button>; const clonedElement = cloneElement( originalElement, { color: 'red', size: 'large' } // Override/add props ); // Result: <Button color="red" size="large">Click</Button>
Common Use Cases
1. Adding Props to Children
tsxfunction List({ children, onItemClick }) { return ( <ul> {Children.map(children, (child, index) => cloneElement(child, { onClick: () => onItemClick(index), 'data-index': index }) )} </ul> ); } // Usage <List onItemClick={handleClick}> <li>Item 1</li> <li>Item 2</li> </List>
2. Injecting Refs
tsxfunction FocusManager({ children }) { const ref = useRef(null); useEffect(() => { ref.current?.focus(); }, []); return cloneElement(children, { ref }); } // Usage <FocusManager> <input placeholder="Auto-focused" /> </FocusManager>
3. Wrapping with Additional Behavior
tsxfunction Tooltip({ children, content }) { const [show, setShow] = useState(false); return ( <> {cloneElement(children, { onMouseEnter: () => setShow(true), onMouseLeave: () => setShow(false) })} {show && <TooltipContent>{content}</TooltipContent>} </> ); }
Replacing Children
Pass new children as the third argument:
tsxconst original = <div className="box">Old content</div>; const cloned = cloneElement( original, null, // Keep existing props 'New content', // Replace children <span>More content</span> );
⚠️ When NOT to Use cloneElement
cloneElement makes data flow harder to trace. Prefer alternatives:
Instead of cloneElement:
tsx// 🔴 Implicit prop injection function Parent({ children }) { return cloneElement(children, { theme: 'dark' }); } // ✅ Explicit render prop function Parent({ children }) { return children({ theme: 'dark' }); } // ✅ Context function Parent({ children }) { return ( <ThemeContext.Provider value="dark"> {children} </ThemeContext.Provider> ); }