DEV Community

Abhay Singh Kathayat
Abhay Singh Kathayat

Posted on

Mastering Compound Components in React for Flexible and Reusable UIs

Compound Components in React

Compound components are a powerful React pattern used to create more flexible and reusable component APIs. In this pattern, a parent component manages state and behavior, while the child components (which are passed as part of the parent’s children) render specific parts of the UI. The parent component controls the logic, while the children components control how to display that logic.

This pattern is especially useful when you need to share state and behavior between components in a controlled way but still want to allow flexibility in how the child components are rendered.

How Compound Components Work

In a compound component, the parent component is responsible for managing the state and passing that state down to its children via context or props. The child components use this shared state and can render different parts of the UI based on the data they receive.

Key Principles:

  • The parent component holds state.
  • The children components are provided with necessary state and behavior via props or React Context.
  • The components can be composed to form a complex structure but are still independent of one another.

This pattern makes it easier to handle shared state and behavior between nested components without the need for prop drilling.


Example of Compound Components

Here’s an example of a compound component pattern for building a Tabs component:

Tabs (Parent Component)

import React, { createContext, useState } from 'react';

// Context to manage active tab
const TabContext = createContext();

const Tabs = ({ children }) => {
  const [activeTab, setActiveTab] = useState(0);

  const handleTabChange = (index) => {
    setActiveTab(index);
  };

  return (
    <TabContext.Provider value={{ activeTab, handleTabChange }}>
      <div>{children}</div>
    </TabContext.Provider>
  );
};

export { Tabs, TabContext };
Enter fullscreen mode Exit fullscreen mode

Tab (Child Component)

import React, { useContext } from 'react';
import { TabContext } from './Tabs';

const Tab = ({ index, children }) => {
  const { activeTab, handleTabChange } = useContext(TabContext);

  return (
    <div
      style={{
        display: activeTab === index ? 'block' : 'none',
      }}
    >
      {children}
    </div>
  );
};

export default Tab;
Enter fullscreen mode Exit fullscreen mode

TabList (Child Component)

import React from 'react';

const TabList = ({ children }) => {
  return <div>{children}</div>;
};

export default TabList;
Enter fullscreen mode Exit fullscreen mode

TabButton (Child Component)

import React, { useContext } from 'react';
import { TabContext } from './Tabs';

const TabButton = ({ index, children }) => {
  const { activeTab, handleTabChange } = useContext(TabContext);

  return (
    <button
      onClick={() => handleTabChange(index)}
      style={{
        fontWeight: activeTab === index ? 'bold' : 'normal',
      }}
    >
      {children}
    </button>
  );
};

export default TabButton;
Enter fullscreen mode Exit fullscreen mode

Usage of Compound Components

import React from 'react';
import { Tabs, Tab } from './Tabs';
import TabList from './TabList';
import TabButton from './TabButton';

const App = () => {
  return (
    <Tabs>
      <TabList>
        <TabButton index={0}>Tab 1</TabButton>
        <TabButton index={1}>Tab 2</TabButton>
        <TabButton index={2}>Tab 3</TabButton>
      </TabList>
      <Tab index={0}>Content of Tab 1</Tab>
      <Tab index={1}>Content of Tab 2</Tab>
      <Tab index={2}>Content of Tab 3</Tab>
    </Tabs>
  );
};

export default App;
Enter fullscreen mode Exit fullscreen mode

How It Works:

  1. Tabs: The parent component (Tabs) holds the state (activeTab) and passes the state to its children through the TabContext.
  2. TabList: A wrapper for the TabButton components.
  3. TabButton: Each button is responsible for changing the active tab when clicked. The active tab index is passed down via context and the button adjusts its style accordingly.
  4. Tab: The Tab component is responsible for displaying the content when its index is active. The content is conditionally rendered based on the active tab.

Why Use Compound Components?

  1. Improved Composition: Compound components allow you to create more flexible and reusable components by providing a customizable API.
  2. Avoid Prop Drilling: By using context, you can avoid passing props manually down through many layers of components. This helps in scenarios where you have deeply nested components.
  3. Centralized State Management: The parent component can manage all the logic and state, while children focus purely on rendering, which improves maintainability.
  4. Customizability: The child components are free to be customized and provide different UI representations as long as they adhere to the logic provided by the parent.

Benefits of Using Compound Components

  1. Clear Separation of Concerns: The parent handles the logic and state, while the children are only responsible for rendering, making the code easier to maintain.
  2. Flexibility in Design: Since the children components are decoupled from the parent, you can easily swap out or adjust the rendering of UI elements.
  3. Cleaner API: Compound components make it easier to manage complex UIs by encapsulating related elements together, making the codebase more intuitive and organized.
  4. Reusability: Components that follow the compound pattern are highly reusable in different contexts. You can compose new UIs by simply using the same child components but in different combinations.

Conclusion

The compound component pattern is an excellent way to manage shared state and behavior across components while leaving room for customization and flexibility in rendering. It is particularly useful for creating highly reusable and composable UI elements like forms, tabs, accordions, and modals. By leveraging this pattern, developers can make their React components more maintainable, modular, and adaptable to different use cases.


Top comments (0)