In React.js, Compound Components is an advanced design pattern that allows you to build components that are more flexible and customizable. This pattern is particularly useful when you want to give users the freedom to compose and structure components in a variety of ways while keeping the code organized and reusable.
Compound Components are a way to create React components that work together as a cohesive unit. They allow you to design a group of related components that communicate with each other through a shared context. This pattern is especially useful for building components like tabs, modals, dropdown menus, or forms where the parent component controls the behavior and the children manage specific tasks.
In simpler terms, a Compound Component pattern allows you to break down a component into smaller parts, which are self-contained but work together when combined.
Let’s start with a simple example of a Compound Component that implements a toggle button.
We want to build a toggle button with two components: Toggle
(the parent) and ToggleOn
, ToggleOff
(the children). The parent will control the state, and the children will display content based on the state.
import React, { useState } from 'react';
const Toggle = ({ children }) => {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);
return React.Children.map(children, child => {
if (typeof child.type === 'function') {
return React.cloneElement(child, { on, toggle });
}
return child;
});
};
const ToggleOn = ({ on, children }) => on ? <>{children}> : null;
const ToggleOff = ({ on, children }) => !on ? <>{children}> : null;
const ToggleButton = ({ toggle }) => ;
const App = () => (
The button is ON
The button is OFF
);
Toggle Component
: This is the parent component that manages the toggle state (on
or off
). It uses the React.Children.map
method to pass down the state (on
) and the toggle
function to its children.ToggleOn and ToggleOff
: These are child components that decide what to render based on the on
state. If on
is true, ToggleOn
shows its content; if false, ToggleOff
shows its content.ToggleButton
: A button component that calls the toggle
function to change the state.Now that we understand the basic structure, let’s dive deeper into the internal workings of Compound Components and explore more advanced techniques.
One of the core ideas behind Compound Components is state sharing. In our previous example, the Toggle
component held the state (on
) and passed it down to ToggleOn
, ToggleOff
, and ToggleButton
. This allowed these child components to act based on the parent’s state.
This state-sharing is usually done using React Context when the structure becomes more complex.
For large components, passing props manually can become cumbersome. Instead, we can use React Context to share state between parent and child components without passing props directly. This makes the code more scalable.
Let’s update our toggle example using Context API.
import React, { useState, createContext, useContext } from 'react';
// Create a Context
const ToggleContext = createContext();
// Toggle Provider to share state with children
const Toggle = ({ children }) => {
const [on, setOn] = useState(false);
const toggle = () => setOn(!on);
return (
{children}
);
};
// Custom hook to access Toggle Context
const useToggle = () => {
const context = useContext(ToggleContext);
if (!context) {
throw new Error("useToggle must be used within a Toggle");
}
return context;
};
const ToggleOn = ({ children }) => {
const { on } = useToggle();
return on ? <>{children}> : null;
};
const ToggleOff = ({ children }) => {
const { on } = useToggle();
return !on ? <>{children}> : null;
};
const ToggleButton = () => {
const { toggle } = useToggle();
return ;
};
const App = () => (
The button is ON
The button is OFF
);
ToggleContext
: This context provides the on
state and toggle
function to all child components without having to pass props.useToggle
: A custom hook that allows any component inside the Toggle
component to access the context values.Toggle
Provider: It wraps the children with ToggleContext.Provider
to share the state (on
) and the toggle
function.This behaves the same as the previous example, but now the state and functions are shared more cleanly using Context.
One powerful feature of Compound Components is the ability to handle dynamic children. Instead of hardcoding child components like ToggleOn
and ToggleOff
, you can make your compound components more flexible by allowing dynamic content.
const App = () => (
The button is ON
The button is OFF
Some other content
);
In this example, Toggle
will work with any content that is passed to it, not just ToggleOn
or ToggleOff
.
You can also allow users to customize the behavior of compound components using props. For example, you might want to allow users to control how the Toggle
component behaves when clicked.
const Toggle = ({ children, initialOn = false }) => {
const [on, setOn] = useState(initialOn);
const toggle = () => setOn(!on);
return React.Children.map(children, child => {
return React.cloneElement(child, { on, toggle });
});
};
Now, users of the Toggle
component can pass an initialOn
prop to control the initial state.
The button is ON
The button is OFF
Let’s build a more complex example to see how Compound Components can be used in a real-world scenario. We’ll create a Tab Component with multiple tabs that users can click to view different content.
import React, { useState, createContext, useContext } from 'react';
// Create Context
const TabContext = createContext();
const Tabs = ({ children }) => {
const [activeIndex, setActiveIndex] = useState(0);
return (
{children}
);
};
const TabList = ({ children }) => {
return {children};
};
const Tab = ({ index, children }) => {
const { activeIndex, setActiveIndex } = useContext(TabContext);
const isActive = activeIndex === index;
return (
);
};
const TabPanels = ({ children }) => {
const { activeIndex } = useContext(TabContext);
return {children[activeIndex]};
};
const TabPanel = ({ children }) => {
return {children};
};
// Usage
const App = () => (
Tab 1
Tab 2
Tab 3
Content 1
Content 2
Content 3
);
Tabs
: This is the parent component that manages the active tab index.TabList
: A wrapper for the list of tabs.Tab
: Each individual tab button. When clicked, it sets the active tab.TabPanels
: The container for the content of each tab. It renders the content of the active tab.TabPanel
: Individual panel content corresponding to each tab.Compound Components are a powerful pattern in React.js that allow you to create flexible, reusable, and scalable UI components. By leveraging concepts like state sharing and the Context API, you can make components that work together seamlessly while offering flexibility to the developer using them. Happy Coding!❤️