Design Converter
Education
Last updated on Feb 24, 2025
•5 mins read
Last updated on Feb 24, 2025
•5 mins read
Software Development Executive - I
Builds things that work. And if it doesn’t, he’ll fix it — with Neovim, of course.
Struggling with prop drilling in React? Want a cleaner way to manage child and parent components?
Compound components in React offer a smart way to build flexible and reusable UI elements. This pattern lets child components communicate with their parent without passing too many props.
The result?
A more structured and maintainable component tree.
This blog breaks down compound components, how to use the Context API with them, and why they work better than traditional component APIs. Code examples will show how to create scalable components while improving state management and reducing unnecessary re-renders.
A compound component is a set of multiple components that work together as a single unit. Instead of using props to pass data between individual components, they share a context value, enabling implicit communication.
A basic tabs component typically passes props to manage active states:
1function Tabs({ activeTab, onChange }) { 2 return ( 3 <div> 4 <button onClick={() => onChange(0)}>Tab 1</button> 5 <button onClick={() => onChange(1)}>Tab 2</button> 6 <div>{activeTab === 0 ? "Content 1" : "Content 2"}</div> 7 </div> 8 ); 9}
This approach requires passing props manually, which becomes cumbersome in a large component tree.
Prop drilling forces components to pass data through multiple levels. Using compound components removes this by leveraging the context API.
With compound components, the parent component controls child components without explicitly passing props.
By designing flexible structures, reusable components can be created without unnecessary dependencies.
To manage shared state, a context API is used.
1import { createContext, useContext, useState } from "react"; 2 3const TabsContext = createContext(); 4 5function TabsProvider({ children }) { 6 const [activeTab, setActiveTab] = useState(0); 7 8 return ( 9 <TabsContext.Provider value={{ activeTab, setActiveTab }}> 10 {children} 11 </TabsContext.Provider> 12 ); 13} 14 15function useTabsContext() { 16 return useContext(TabsContext); 17}
Here, a context is created to store the active tab index.
The parent component (Tabs) will wrap all child components.
1function Tabs({ children }) { 2 return <TabsProvider>{children}</TabsProvider>; 3}
This ensures that all child components can access the context value.
A select component to switch tabs:
1function TabList({ children }) { 2 return <div>{children}</div>; 3} 4 5function Tab({ index, children }) { 6 const { activeTab, setActiveTab } = useTabsContext(); 7 return ( 8 <button 9 onClick={() => setActiveTab(index)} 10 style={{ fontWeight: activeTab === index ? "bold" : "normal" }} 11 > 12 {children} 13 </button> 14 ); 15}
Each select component interacts with the context value to update the active tab.
The content component will conditionally render the active tab content.
1function TabPanel({ index, children }) { 2 const { activeTab } = useTabsContext(); 3 return activeTab === index ? <div>{children}</div> : null; 4}
Now, the tabs component can be used like this:
1function App() { 2 return ( 3 <Tabs> 4 <TabList> 5 <Tab index={0}>Tab 1</Tab> 6 <Tab index={1}>Tab 2</Tab> 7 </TabList> 8 <TabPanel index={0}>Content for Tab 1</TabPanel> 9 <TabPanel index={1}>Content for Tab 2</TabPanel> 10 </Tabs> 11 ); 12} 13 14export default App;
This example shows how multiple components work together while avoiding prop drilling.
By separating elements, content components, and behavior, the compound components pattern makes UI structures more flexible.
Since context API is used, only components that rely on the context value will render, reducing unnecessary re-renders.
Instead of explicitly passing props, implicit state is used, making the API cleaner.
A more advanced pattern involves nested compound components, such as a toggle component.
1const ToggleContext = createContext(); 2 3function Toggle({ children }) { 4 const [on, setOn] = useState(false); 5 return ( 6 <ToggleContext.Provider value={{ on, setOn }}> 7 {children} 8 </ToggleContext.Provider> 9 ); 10} 11 12function ToggleButton() { 13 const { on, setOn } = useContext(ToggleContext); 14 return <button onClick={() => setOn(!on)}>{on ? "ON" : "OFF"}</button>; 15} 16 17function ToggleContent({ children }) { 18 const { on } = useContext(ToggleContext); 19 return on ? <div>{children}</div> : null; 20} 21 22function App() { 23 return ( 24 <Toggle> 25 <ToggleButton /> 26 <ToggleContent>Visible when ON</ToggleContent> 27 </Toggle> 28 ); 29}
This example showcases an advanced pattern where child components dynamically render based on shared state.
Each root component must have a provider, or child components will not access the context value.
If state management does not require shared state, avoid adding unnecessary context.
The children prop should be used appropriately to ensure only direct children access the context value.
The compound components pattern is a powerful technique for structuring React applications. It eliminates prop drilling, improves state management, and makes component APIs cleaner.
By understanding how compound components function, developers can create well-structured React applications while maintaining control over child components.
Start using the compound component pattern in your next project and experience the benefits firsthand!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.