Three Patterns for Passing React Components as Props and Their Application Scenarios

Nov 21, 2025 · Programming · 10 views · 7.8

Keywords: React Component Passing | Prop Patterns | Component Composition

Abstract: This article provides an in-depth analysis of three main patterns for passing React components as props: as React elements, as component classes, and as render functions. Through detailed code examples and scenario analysis, it explains the characteristics, applicable scenarios, and best practices of each pattern, helping developers choose the most suitable implementation based on specific requirements.

Introduction

In React development, component reuse and composition are core to building complex applications. When needing to pass components between other components, developers face multiple choices. This article systematically analyzes three main passing patterns based on React community best practices, demonstrating the advantages and limitations of each through practical scenarios.

Basic Concepts of Component Passing

React provides flexible component composition mechanisms, with props.children being the most commonly used passing method. When nesting child components within a parent component, these children can be accessed via props.children. For example:

const Label = props => <span>{props.children}</span>
const Tab = props => <div>{props.children}</div>
const Page = () => <Tab><Label>Foo</Label></Tab>

This pattern works well for simple component nesting, but when more precise control over component placement or passing multiple components is needed, direct prop passing becomes a better choice.

Pattern One: Components as React Elements

This pattern passes already instantiated React elements as prop values. The receiving component can render these elements directly without additional instantiation steps.

const ButtonWithIconElement = ({ children, icon }) => {
  return (
    <button>
      {icon}
      {children}
    </button>
  );
};

// Usage example
<ButtonWithIconElement icon={<AccessAlarmIcon />}>
  Button text
</ButtonWithIconElement>

Advantages: Simple implementation, direct passing of rendered results, suitable for static component configuration.

Limitations: When needing to modify passed component props, React.cloneElement must be used, increasing complexity.

Pattern Two: Components as Component Classes

This pattern passes component classes (uninstantiated), with the receiving component responsible for instantiation. Prop names should start with capital letters, following React component naming conventions.

const ButtonWithIconComponent = ({ children, Icon }) => {
  return (
    <button>
      <Icon />
      {children}
    </button>
  );
};

// Usage example
<ButtonWithIconComponent Icon={AccessAlarmIcon}>
  Button text
</ButtonWithIconComponent>

Advantages: Receiving component has full control over instantiation process, facilitating default prop passing.

Considerations: If passed components need to wrap other components, props must be properly forwarded:

const CustomIcon = (props) => (
  <AccessAlarmIcon {...props} color="error" />
);

Pattern Three: Components as Render Functions

This pattern passes a function that returns React elements, typically prefixed with render to clarify its purpose.

const ButtonWithIconRenderFunc = ({ children, renderIcon }) => {
  const icon = renderIcon();
  return (
    <button>
      {icon}
      {children}
    </button>
  );
};

// Usage example
<ButtonWithIconRenderFunc
  renderIcon={() => <AccessAlarmIcon />}
>
  Button text
</ButtonWithIconRenderFunc>

Advantages: Functions can receive parameters, enabling dynamic rendering, suitable for scenarios requiring context-aware rendering adjustments.

Advanced Application Scenario Comparison

Default Prop Setting

Implementation approaches for setting default props vary across patterns:

Element Pattern: Requires React.cloneElement:

const clonedIcon = React.cloneElement(icon, {
  fontSize: icon.props.fontSize || 'small',
});

Component Pattern: Directly pass props during instantiation:

<Icon fontSize="small" />

Function Pattern: Pass configuration through function parameters:

const icon = renderIcon({ fontSize: 'small' });

Dynamic Interaction Handling

Using button hover effects as an example, demonstrating implementation differences across patterns:

Component Pattern Implementation:

const ButtonWithHover = ({ children, Icon }) => {
  const [isHovered, setIsHovered] = useState(false);
  
  return (
    <button
      onMouseOver={() => setIsHovered(true)}
      onMouseOut={() => setIsHovered(false)}
    >
      <Icon isHovered={isHovered} />
      {children}
    </button>
  );
};

// Custom icon component
const DynamicIcon = ({ isHovered, ...props }) => (
  <AccessAlarmIcon
    {...props}
    color={isHovered ? 'primary' : 'warning'}
  />
);

Pattern Selection Guidelines

Based on the above analysis, the following selection recommendations are provided:

In actual projects, consistency is more important than pattern choice. Teams should establish unified standards to avoid confusion from using multiple patterns within the same project.

Performance Considerations

Regardless of the chosen pattern, the following performance optimization points should be noted:

Conclusion

The three patterns for passing React components as props each have their applicable scenarios, with no absolute superiority. Developers should choose the most suitable implementation based on specific requirements and maintain consistency within their teams. By deeply understanding the characteristics and limitations of each pattern, more flexible and maintainable React application architectures can be built.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.