unknown39825

Advance React Chapter 3

This chapter delves into React.memo and the useMemo hook, exploring how to prevent unnecessary re-renders when components receive the same props or when expensive calculations need to be cached.

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

  1. Elements as props solve configuration complexity by moving control to the consumer
  2. Performance is not a concern - element creation is cheap, rendering happens only when needed
  3. Default props can be added using cloneElement, but be careful with prop merging order
  4. This pattern works well for:
    • Layout components
    • Modal dialogs
    • Any component where content varies significantly
  5. 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