Master Flexible React Components with Compound Component Patterns
This article demonstrates how to transform a basic Stepper component into a highly reusable and configurable React component using the compound component pattern, static properties, and React's Children utilities, while addressing common customization challenges and preparing for context API integration.
To celebrate the release of React 16.3, the author shares techniques that dramatically change how React components are built, enabling fully reusable components that can be flexibly used in many environments.
Using Compound Component Design Pattern
The initial Stepper component is simple but inflexible because its stage prop controls the progress and cannot be easily altered. The author lists typical customization questions such as moving the progress block, adding extra stages, changing stage content, or reordering stages.
import React, { Component } from 'react';
import Stepper from "./Stepper";
class App extends Component {
render() {
return (
<Stepper stage={1} />
);
}
}
export default App;To make the component flexible, the article introduces the compound component pattern. The Stepper class stores the current stage in state, provides a handleClick method to advance the stage, and renders a Progress and Steps component.
class Stepper extends Component {
state = { stage: this.props.stage };
static defaultProps = { stage: 1 };
handleClick = () => {
this.setState({ stage: this.state.stage + 1 });
};
render() {
const { stage } = this.state;
return (
<div style={styles.container}>
<Progress stage={stage} />
<Steps handleClick={this.handleClick} stage={stage} />
</div>
);
}
}
export default Stepper;Instead of hard‑coding Progress and Steps inside Stepper, the article shows how to use props.children to insert them dynamically, allowing the parent to decide their order and placement.
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { stage, handleClick: this.handleClick })
);
return <div style={styles.container}>{children}</div>;This approach gives the ability to render components on either side of the progress bar, but it introduces a new problem: the child components lose direct access to stage and handleClick. To solve this, the article uses React.Children.map and React.cloneElement to inject the needed props into each child.
const children = React.Children.map(this.props.children, child => {
return React.cloneElement(child, { stage, handleClick: this.handleClick });
});Static properties are then introduced to improve readability. By assigning Progress and Steps as static members of Stepper, they can be accessed as Stepper.Progress and Stepper.Steps without separate imports.
class Stepper extends Component {
// ...component code
static Progress = Progress;
static Steps = Steps;
}The usage in App becomes concise:
<Stepper stage={1}>
<Stepper.Progress />
<Stepper.Steps />
</Stepper>Further, the article demonstrates adding a static Stage component to render individual steps, allowing any number of stages with custom text and order.
class Progress extends Component {
render() {
const { stage } = this.props;
const children = React.Children.map(this.props.children, child =>
React.cloneElement(child, { stage })
);
return <div style={styles.progressContainer}>{children}</div>;
}
}For animated step transitions, the Steps component wraps matching children in Transition components using ReactTransitionGroup. It only renders a child when its num matches the current stage.
class Steps extends Component {
render() {
const { stage, handleClick } = this.props;
const children = React.Children.map(this.props.children, child => (
stage === child.props.num && (
<Transition appear={true} timeout={300} onEntering={entering} onExiting={exiting}>
{child}
</Transition>
)
));
return (
<div style={styles.stagesContainer}>
<div style={styles.stages}>
<TransitionGroup>{children}</TransitionGroup>
</div>
<div style={styles.stageButton}>
<Button disabled={stage===4} click={handleClick}>Continue</Button>
</div>
</div>
);
}
}
export default Steps;An attempt to insert a title above Stepper.Steps breaks the component hierarchy because Steps is no longer a direct child of Stepper and thus cannot receive the stage prop.
The article emphasizes that React 16.3’s new Context API (still experimental at the time) will solve this limitation by allowing any component in the tree to access shared data, a topic that will be covered in part 2.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
