Component Polymorphism in React
Tags: react, typescript, webdev
Published: 2022-01-03 14:18:26

What's Up, people! Hope you're doing fine!

![hello](https://media.giphy.com/media/3ornk57KwDXf81rjWM/giphy.gif)

In this article, I'm going to explain Polymorphic Components in React, along with their implementation and using them with Typescript!

So, there is a high chance that you might not be familiar with this concept. But you may have encountered this pattern.

In a nutshell, this pattern lets us specify which HTML tag to use to render our component.

But, the flexibility of polymorphic components also makes them easy to misuse, and that’s where TypeScript can help us. 

So, let's dive deep into this!

### Overview - Polymorphic Components

First, let’s see how we would use polymorphic components in react. Say we have a `Button` component that we want to render as an HTML link. If Button is a polymorphic component, we can write it like this:

```jsx
import Button from './Button'

function App() {
  return (
    <Button as="a" href="https://open.spotify.com">
  )
}
      
export default App
``` 

Here, our button will render as `a` tag and it also accepts `href` attribute.

### Basic Implementation:

**Note**: To implement this in your react app, you need to have Typescript set up.

Now, let's implement a Basic example for this, without type checking for now:


```jsx
const Button = ({ as, children, ...props }: any) => {
    const Component = as || "button";

    return <Component {...props}>{children}</Component>;
};

export default Button;
``` 

Here, we avoid type checking by setting the type to `any`.

Here, we render our component using the `as` prop or if it's not provided, use the `button` tag as fallback.

Here's the line which makes this work:

```ts
const Component = as || "button";
``` 

That's all we need to build a basic implementation.

However, the problem with this approach is that there is no mechanism to prevent the client from passing incorrect props.

Here's an example:


```tsx
import Button from './Button'

function App(){
  return (
    <Button href="https://open.spotify.com">
  )
}
      
export default App
``` 

Here, we are passing the `href` prop, which belongs to `a` tag, without setting the `as` prop to `a`.
Ideally, TypeScript would catch this bug immediately, and we would see an error.

### Type checking using Typescript!

Next up, we are gonna tighten up the prop type using Typescript.

Here's a basic implementation for this:


```ts
import { ComponentPropsWithoutRef, ElementType, ReactNode } from "react";

type ButtonProps<T extends ElementType> = {
  as?: T;
  children: ReactNode;
};

const Button = <T extends ElementType = "button">({
  as,
  children,
  ...props
}: ButtonProps<T> & ComponentPropsWithoutRef<T>) => {
  const Component = as || "button";

  return <Component {...props}>{children}</Component>;
};

export default Button;
``` 

Here, this code involves generics. The following line made this component generic:


```ts
const Button = <T extends ElementType = "button">
``` 

`ElementType` is a type from React. We set our parameter T to ElementType to ensure our button only accepts HTML tags and other React component types.

At this point, our Button component can dynamically calculate the props it accepts based on the value of as. If we try our client example earlier, we will see an error like this:

![Screenshot (330).png](https://cdn.hashnode.com/res/hashnode/image/upload/v1641050583211/AFT0C_N1N.png)

Here, we get an error stating that `Property 'href' does not exist on type 'IntrinsicAttributes & MyButtonProps<"button">`

That's it! Our Button component no longer accepts the `href` property because it doesn’t render itself as a link. If we add `as="a"`, the error goes away.