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>
elementLet'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>
}
<MyButton
icon="💩"
type="submit"
disabled={...}
onClick={...}
onMouseOver={...}
> CLICK ME </MyButton>