DEV Community 👩‍💻👨‍💻

Cover image for How to create React UIKIT components in TypeScript that extends native HTML Elements
Fabio Biondi
Fabio Biondi

Posted on • Updated on

How to create React UIKIT components in TypeScript that extends native HTML Elements

In the front-end ecosystem, UiKits are collections of utilities and several components, such as:

  • <Card />
  • <TabBar />
  • <Carousel />
  • <Row /> and <Col />
  • <GoogleMap />

Each component has usually its own custom layout and could accepts several properties. For example, a <GoogleMap /> component could accept the "coordinates" and a "zoom" value as properties:

<GoogleMap coords={coords} zoom={zoom} />
Enter fullscreen mode Exit fullscreen mode

Sometimes we need to create components whose layout is created from scratch. In other cases their layout may be based of native elements.
Some examples:

  • <MyButton />: that extends <button> element capabilities
  • <MyImg />: that extends <img> element
  • and so on.

Let's imagine a button component that must display an icon next to the label that I can use as the code below:

<MyButton icon="💩"> CLICK ME </MyButton>
Enter fullscreen mode Exit fullscreen mode

This component should accept the icon and children properties and its definition may looks like the following:

interface MyButtonProps {
  icon: string;
}

function MyButton(props: PropsWithChildren<MyButtonProps>) {
  const { icon, children } = props;
  return <button className="btn btn-primary">
    {icon} {children}
  </button>
}
Enter fullscreen mode Exit fullscreen mode

So we can use the component in this way:

<MyButton icon="💩"> DO SOMETHING</icon>
<MyButton icon="😱"> CLICK ME</icon>
Enter fullscreen mode Exit fullscreen mode

Button Preview


In order to be more flexible, the <MyButton /> component should also accepts all the native button properties. For example we may need to listen the onClick event or set the disabled attribute:

<MyButton icon="💩" onClick={() => {}} disabled />
Enter fullscreen mode Exit fullscreen mode

So we can simply add them to the Component's Property Type and apply them as attributes of the <button> element:

// 1. Add the properties 
interface MyButtonProps {
  icon: string;  
  disabled: boolean;   // ➡️ native prop
  onClick: (e: React.MouseEvent) => void; // ➡️ native prop
  // ➡️ other native props
}

function MyButton(props: PropsWithChildren<MyButtonProps) {

  const { icon, children, onClick, disabled } = props;
  // 2. apply all props one by one
  return <button disabled={disabled} onClick={onClick} className="btn btn-primary">
    {icon}
    {children}
  </button>
}
Enter fullscreen mode Exit fullscreen mode

What could I do to avoid manually passing all properties of a native button?

There is a trick!

We can simply use an intersection type to combine our custom props with all the HTMLButtonElement properties to automatically support all native buttons properties:

export function MyButton(
  props: PropsWithChildren<MyButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>>
) { 
 // ... component here ...
}
Enter fullscreen mode Exit fullscreen mode

Now your component supports all buttons properties and we can apply them simply using this trick:

// apply all props as button attributes
return <button className="btn btn-primary" {...props}>
Enter fullscreen mode Exit fullscreen mode

However icon and children are not valid button's properties so we can use destructuring to create a rest property that contain all the properties except them:

  const { icon, children, ...rest } = props;

  // Now we apply all props except icons and children
  return <button className="btn btn-primary" {...rest}>
    {icon}
    {children}
  </button>
Enter fullscreen mode Exit fullscreen mode

And that's all.
Here the final source code to create a button that support icon, children and all native button properties:

import { PropsWithChildren } from 'react';

interface MyButtonProps {
  icon: string;
}
export function MyButton(
  props: PropsWithChildren<MyButtonProps & React.ButtonHTMLAttributes<HTMLButtonElement>>
) {
  const { icon, children, ...rest } = props;
  return <button className="btn btn-primary" {...rest}>
    {icon}
    {children}
  </button>
}

Enter fullscreen mode Exit fullscreen mode

USAGE EXAMPLE:

<MyButton 
  icon="💩" 
  type="submit"
  disabled={...} 
  onClick={...} 
  onMouseOver={...} 
> CLICK ME </MyButton>
Enter fullscreen mode Exit fullscreen mode

You can also be interested to read this article:
Create a React / TypeScript Generic component


🔗 Follow me on:

Top comments (0)

🌚 Life is too short to browse without dark mode