Logo
Published on

Supercharging React Components with TypeScript and Compound Components

Authors
  • Name
    Twitter

Introduction

When building user interfaces with React and TypeScript, you have a dynamic duo at your disposal. By combining TypeScript’s type safety with React’s component-based architecture, you can create robust and maintainable applications. One powerful design pattern that marries these technologies is compound components. In this article, we’ll explore how to leverage compound components in a React project with TypeScript for maximum reusability, type safety, and maintainability.

Overview

Compound components in React involve the creation of a parent component that orchestrates a set of child components. These child components work together to form a cohesive UI element while allowing for customization and configuration. TypeScript brings static typing into this mix, ensuring that your components are not only modular but also type-safe.

Benefits of Compound Components with TypeScript

1. Type Safety

TypeScript enforces strong typing, reducing runtime errors and making your code more maintainable.

2. Modular Design

Compound components encourage a modular approach, breaking down complex UI elements into smaller, manageable parts.

3. Customization

Users can interact with individual child components, customizing them to suit their specific needs.

Building Compound Components in React with TypeScript

Step 1: Define the Compound Component Structure

Determine the structure of your compound component, identifying the role and responsibilities of each child component.

Step 2: Create Child Components

Develop separate TypeScript components for each part of the compound component, ensuring clear prop typings.

Step 3: Create the Parent Component

In the parent component, import and render the child components, passing necessary props and handling state logic.

Step 4: Define Prop Types

Leverage TypeScript’s prop types to enforce type safety for the parent and child components.

Step 5: Export the Compound Component

Export the parent component as the main compound component that users will import and utilize.

Step 6: Documentation and Examples

Provide comprehensive documentation and usage examples to guide users in interacting with your compound component.

Example: Compound Component — Accordion

As an example, let’s build a compound component called Accordion. The Accordion will consist of AccordionItem child components, each representing a collapsible section with customizable content.

Accordion.tsx

import React, { FC, ReactNode, useState } from "react";  
  
import "./Accordion.css";  
  
/**  
 * Props for the Accordion component.  
 */  
interface AccordionProps {  
  children: ReactNode;  
}  
  
/**  
 * Props for the AccordionItem component.  
 */  
interface AccordionItemProps {  
  index: number;  
  label: string;  
  children: ReactNode;  
  activeIndex?: number;  
  handleItemClick?: (index: number) => void;  
}  
  
/**  
 * A compound component representing an accordion.  
 */  
interface AccordionComponent extends FC<AccordionProps> {  
  Item: FC<AccordionItemProps>;  
}  
  
/**  
 * The Accordion component.  
 *  
 * @param {AccordionProps} props - The component props.  
 * @returns {JSX.Element} The rendered Accordion component.  
 */  
const Accordion: AccordionComponent = ({  
  children,  
}: AccordionProps): JSX.Element => {  
  const [activeIndex, setActiveIndex] = useState<number>(-1);  
  
  /**  
   * Handle the click event on an accordion item.  
   *  
   * @param {number} index - The index of the clicked item.  
   */  
  const handleItemClick = (index: number) => {  
    setActiveIndex((prevActiveIndex) =>  
      prevActiveIndex === index ? -1 : index  
    );  
  };  
  
  return (  
    <div>  
      {React.Children.map(children, (child, index) => {  
        if (React.isValidElement<AccordionItemProps>(child)) {  
          return React.cloneElement(child, {  
            index,  
            activeIndex,  
            handleItemClick,  
          });  
        }  
        return child;  
      })}  
    </div>  
  );  
};  
  
/**  
 * The AccordionItem component.  
 *  
 * @param {AccordionItemProps} props - The component props.  
 * @returns {JSX.Element} The rendered AccordionItem component.  
 */  
const AccordionItem: FC<AccordionItemProps> = ({  
  index,  
  label,  
  activeIndex,  
  handleItemClick,  
  children,  
}: AccordionItemProps): JSX.Element => {  
  /**  
   * Handle the click event on an accordion item button.  
   */  
  const handleClick = () => {  
    handleItemClick?.(index);  
  };  
  
  return (  
    <div className="accordion">  
      <button onClick={handleClick} className="accordion-button">  
        {label}  
      </button>  
      {activeIndex === index && <div>{children}</div>}  
    </div>  
  );  
};  
  
// Attach the AccordionItem component as a property of the Accordion component.  
Accordion.Item = AccordionItem;  
  
export default Accordion;

App.tsx

import React from "react";  
  
import Accordion from "./components/Accordion";  
  
import "./App.css";  
  
const App: React.FC = () => {  
  return (  
    <Accordion>  
      <Accordion.Item index={0} label="Section 1">  
        Content for Section 1  
      </Accordion.Item>  
      <Accordion.Item index={1} label="Section 2">  
        Content for Section 2  
      </Accordion.Item>  
      <Accordion.Item index={2} label="Section 3">  
        Content for Section 3  
      </Accordion.Item>  
    </Accordion>  
  );  
};  
  
export default App;

In this illustrative example, we’re constructing an Accordion componentusing the compound component pattern. The Accordion component serves as the higher-level container, handling the contextual logic and state management. On the other hand, the AccordionItem components specialize in rendering each collapsible section, complete with content that can be tailored to specific requirements.

Enhancing the User Experience with Styling

To make our accordion visually appealing, we’ve applied modern and responsive styles. You can find the complete styling details in the GitHub repository. The styles include attention-grabbing colors, hover effects, and smooth transitions that create a delightful user experience.

Exploring the Source Code and Demo

For a hands-on experience and deeper understanding, take a look at the full source code and interactive demo available on GitHub. You can explore how the compound component pattern is implemented, experiment with customization, and even integrate the component into your own projects.

Conclusion

In the world of React and TypeScript, compound components are a formidable pattern for creating reusable and customizable UI elements. By leveraging TypeScript’s type safety and React’s component-based architecture, you can build compound components that are not only modular but also error-resistant. Whether you’re developing a UI library or enhancing your application’s maintainability, compound components in React with TypeScript are a powerful tool in your toolkit for building sophisticated and maintainable user interfaces.

Thanks for reading

  • 📰 View more solutions and tips for practical use in job here
  • 🔔 Follow me: LinkedIn | GitHub