How to create React UIKIT components in TypeScript that extends native HTML Elements
Fabio Biondi
Posted on July 8, 2022
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} />
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>
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>
}
So we can use the component in this way:
<MyButton icon="š©"> DO SOMETHING</icon>
<MyButton icon="š±"> CLICK ME</icon>
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 />
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>
}
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 ...
}
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}>
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>
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>
}
USAGE EXAMPLE:
<MyButton
icon="š©"
type="submit"
disabled={...}
onClick={...}
onMouseOver={...}
> CLICK ME </MyButton>
You can also be interested to read this article:
Create a React / TypeScript Generic component
š Follow me on:
Posted on July 8, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.