Chapter 3: Configuration Concerns with Elements as Props
The Problem: Component Configuration Complexity
Example: Button Component Evolution
- Initial requirement: Button with loading icon
- Problem: Requirements keep expanding
- Support all icons (not just loading)
- Control icon color, size, position
- Support avatars instead of icons
- Result: Props explosion and unmaintainable code
// BAD: Too many configuration props
const Button = ({
isLoading,
iconLeftName,
iconLeftColor,
iconLeftSize,
isIconLeftAvatar,
// ... many more props
}) => {
// Complex, hard-to-understand logic
return ...
}
The Solution: Elements as Props
Basic Pattern
Instead of configuration props, pass the entire element:
// GOOD: Simple and flexible
const Button = ({ icon }) => {
return <button>Submit {icon}</button>;
};
// Usage examples
<Button icon={<Loading />} />
<Button icon={<Error color="red" />} />
<Button icon={<Warning color="yellow" size="large" />} />
<Button icon={<Avatar />} />
Benefits
- Flexibility: Consumers control all aspects of configuration
- Simplicity: Component doesn't need to know about specific configurations
- Maintainability: No prop explosion or complex internal logic
Real-World Applications
Modal Dialog
const ModalDialog = ({ children, footer }) => {
return (
<div className="modal-dialog">
<div className="content">{children}</div>
<div className="footer">{footer}</div>
</div>
);
};
// Usage
<ModalDialog footer={<><SubmitButton /><CancelButton /></>}>
<SomeFormHere />
</ModalDialog>
Layout Components
<ThreeColumnsLayout
leftColumn={<Something />}
middleColumn={<OtherThing />}
rightColumn={<SomethingElse />}
/>
Performance Considerations
Key Insight: Element Creation vs. Rendering
- Creating elements (JSX) is cheap - just creates objects in memory
- Rendering components is expensive - actual DOM operations
- Elements are only rendered when they appear in a component's return statement
Conditional Rendering Example
const App = () => {
const [isDialogOpen, setIsDialogOpen] = useState(false);
// This creates an element but doesn't render it
const footer = <Footer />;
// Footer only renders when dialog is open
return isDialogOpen ? (
<ModalDialog footer={footer} />
) : null;
};
Default Values with cloneElement
The Problem
Sometimes you need component control over passed elements while maintaining flexibility.
The Solution
Use React.cloneElement
to add default props:
const Button = ({ appearance, size, icon }) => {
const defaultIconProps = {
size: size === 'large' ? 'large' : 'medium',
color: appearance === 'primary' ? 'white' : 'black',
};
const newProps = {
...defaultIconProps,
...icon.props, // Original props override defaults
};
const clonedIcon = React.cloneElement(icon, newProps);
return <button>Submit {clonedIcon}</button>;
};
Usage
// Uses default white color for primary button
<Button appearance="primary" icon={<Loading />} />
// Override default with custom color
<Button appearance="secondary" icon={<Loading color="red" />} />
Pitfalls and Warnings
Common Mistake: Overriding Props
// DON'T DO THIS - overwrites all original props
const clonedIcon = React.cloneElement(icon, defaultIconProps);
// DO THIS - original props override defaults
const newProps = { ...defaultIconProps, ...icon.props };
const clonedIcon = React.cloneElement(icon, newProps);
When to Avoid cloneElement
- Pattern is fragile and easy to get wrong
- Use only for simple cases
- Consider render props pattern as alternative (covered in next chapter)
Key Takeaways
- Elements as props solve configuration complexity by moving control to the consumer
- Performance is not a concern - element creation is cheap, rendering happens only when needed
- Default props can be added using
cloneElement
, but be careful with prop merging order - This pattern works well for:
- Layout components
- Modal dialogs
- Any component where content varies significantly
- Use children prop for "main" content to improve API ergonomics
Best Practices
- Use elements as props when configuration becomes complex
- Prefer
children
prop for primary content - Be extremely careful with
cloneElement
- always merge props correctly - Consider this pattern for maximum flexibility, traditional props for strict control
- Remember: elements are just objects until they're rendered