Compound Components
Introduction
In this article, we're going to delve into the exciting world of Compound Components, a powerful pattern in React that allows for more flexible and intuitive component structures. This pattern is widely used in popular libraries such as React Router and Downshift, and understanding it will help you write more maintainable and cleanly-structured code.
What are Compound Components?
In simple terms, Compound Components is a pattern where components are used together such that they share an implicit state that lets them communicate with each other. This pattern is perfect for creating components with a shared state that's implicitly shared rather than explicitly passed down through props.
A real-world analogy for compound components would be a form. A form (parent component) consists of form fields (child components). The form fields don't need to know exactly what happens when they get filled out, all they need to know is that they need to notify the form when they are changed.
Implementing a Basic Compound Component
Let's dive into code with a basic example. We'll create a simple Toggle
component that either shows or hides its content based on its state.
import React, { Component } from "react";
class Toggle extends Component {
state = {
on: false,
};
toggle = () => {
this.setState(prevState => ({ on: !prevState.on }));
};
render() {
return this.props.children({
on: this.state.on,
toggle: this.toggle,
});
}
}
export default Toggle;
Here, we're using the function as child pattern to pass down the on
state and the toggle
method to the child components.
We can now use this Toggle
component to show or hide any content.
import React from "react";
import Toggle from "./Toggle";
function App() {
return (
<Toggle>
{({ on, toggle }) => (
<div>
{on && <h1>Show me when I'm toggled.</h1>}
<button onClick={toggle}>Show/Hide</button>
</div>
)}
</Toggle>
);
}
export default App;
Adding More Flexibility
The above example is straightforward, but it's not very flexible. We can't control the structure of the content inside the Toggle
component. Let's try to implement the same but with more flexibility.
import React, { Component } from "react";
class Toggle extends Component {
static On = ({ on, children }) => (on ? children : null);
static Off = ({ on, children }) => (on ? null : children);
static Button = ({ toggle, ...props }) => <button onClick={toggle} {...props} />;
state = { on: false };
toggle = () => this.setState(({ on }) => ({ on: !on }));
render() {
return React.Children.map(this.props.children, childElement =>
React.cloneElement(childElement, {
on: this.state.on,
toggle: this.toggle,
})
);
}
}
export default Toggle;
Here, we've added three static components to the Toggle
component. We then use React.Children.map
and React.cloneElement
to clone the child elements and pass down the on
state and toggle
method.
Now, we can use the Toggle
component like this:
import React from "react";
import Toggle from "./Toggle";
function App() {
return (
<Toggle>
<Toggle.On>The button is on</Toggle.On>
<Toggle.Off>The button is off</Toggle.Off>
<Toggle.Button />
</Toggle>
);
}
export default App;
Conclusion
Compound components promote flexibility and better component communication. They also lead to more readable code since the structure of the component is more declarative. As with any pattern, compound components may not be the best solution for every situation, but understanding them gives you another tool in your React toolbox.