React “Polymorphic Components” With TypeScript
In this article, we are going to learn how to implement polymorphic components in React. Also, we will strongly type our props depending on the type of element we want to render. In our example, we will create a polymorphic button.
So what is the ‘Polymorphic Component’?
In a few words, it is a component that lets us specify which React element we want to use for its root. If you’ve used some UI library, such as Material UI, you’ve already encountered a polymorphic component.
For instance, MUI has a “Typography”, that has the ability to render as any HTML element for the root node. That’s quite a powerful idea that gives us more robust and reusable components.
The result we want to achieve
We want to create a Button, that will be able to behave mainly as a normal button, anchor, and react-router link (or any other HTML tag |component, depending on your requirements). Also, we would like to have all the needed typings for it. For example, the usual button has a “type” attribute and does not have a “href”, but the anchor button has the opposite props.
This code demonstrates to us how we can use our button.
Making our component
Let’s create our file called Button.tsx
and define the base react component.
So what should we do to support more than one type of HTML element for our button? We could achieve this by defining the prop “as”, which will be the type of React.ElementType
and then use it to render our root element.
Notice, that we have to write “Tag” capitalized, otherwise it will be treated as an usual HTML element <tag />.
Now we have a polymorphic button, but we don’t have any props typings for various “as” values. But how will we get them if they depend on dynamic “as” prop?
That is an exact situation where we should use TypeScript generics to type everything properly. So, let’s take a stab at writing some more advanced types.
We have created a new type ButtonOwnProps
containing button base props. Also, it consumes generic E
which extends React.ElementType
and assigns a value to ‘as’.
Next, we should create a type called ButtonProps
, which will combine our base props with generic element props.
In addition, we should exclude props from the generic component which we have already declared in our base type.
In the first line, nothing special happens. We declared a type that receives the generic E
extending React.ElementType
.
In the second line, we just passed it to our base props to get the right as
type.
In the third line, we omitted all the properties from the generic component that we already have in our ButtonOwnProps
.
Final result
Finally, we need to pass generic to our ButtonProps
type and also give it a default value. So the final component will look like this:
That's it for this article. Now our Button
component is ready to be used.
I hope that you liked this article and I was able to give you something new. If you have any questions or suggestions, please write them in the comments.
Good luck!