React + TypeScript: Implementing the Polymorphic Button Component

As developers, creating reusable and flexible components is a crucial aspect of building scalable applications. One powerful technique to achieve this is by developing polymorphic components.

In this article, we'll explore how to create a polymorphic component in React using TypeScript, providing a versatile solution for various scenarios.

Understanding Polymorphic Components

A polymorphic component is one that can adapt its rendered element based on the context or specific requirements. In React, this flexibility is achieved by leveraging TypeScript generics. Let's dive into a practical example using a Button component.

The Code

import React, { ElementType, ComponentProps, PropsWithChildren } from 'react';

export type ButtonProps<AS extends ElementType> =
  PropsWithChildren<{
    as?: AS;
  }> &
    Omit<ComponentProps<AS>, 'as'>;

const Button = <AS extends ElementType>(props: ButtonProps<AS>) => {
  const {
    as: Element = 'button',
    children,
    ...attrs
  } = props;

  return (
    <Element {...attrs}>{children}</Element>
  );
};

The above TypeScript code defines a ButtonProps type, utilizing TypeScript generics to make the component polymorphic. Let's break it down:

  • AS extends ElementType: This generic type parameter allows us to specify the element type that our button should render. It extends the ElementType from React, ensuring that only valid HTML elements or React components can be used.

  • PropsWithChildren: This is a utility type that ensures our component can accept children.

  • { as?: AS }: The as prop is the key to making our component polymorphic. It allows us to specify the element type we want the button to render as. If not provided, it defaults to the standard button HTML element.

  • Omit<ComponentProps<AS>, 'as'>: This utility type is used to exclude the as prop from the props of the specified element type. This is a workaround to achieve the polymorphic behaviour.

Breaking down the Button component:

  • The as prop is destructured with a default value of 'button'. This default ensures that if no specific element is provided, the button will render as a standard HTML button.

  • The component then uses JSX to render the specified element, passing along the rest of the props (...attrs) and rendering the children within it.

Using the Polymorphic Button

Now that we have our polymorphic Button component, let's explore how to use it in different scenarios:

Using as a Standard Button

<Button onClick={() => alert('Clicked!')}>Click me</Button>

Using as an Anchor (<a>) Element

<Button as="a" href="https://example.com">Visit Example</Button>

Conclusion

In this article, we've explored the creation of a polymorphic Button component in React using TypeScript. The flexibility provided by polymorphism allows us to adapt the component to various use cases seamlessly.