React - Create a custom component from native HTML element with typescript
Yacine C
Posted on April 11, 2023
Sometimes we need to overload some native HTML tags with our own component.
Let’s take a simple example.
You need to create a Button
component but you want your button
to have a custom style. You’ll probably write something like :
interface ButtonProps {
value: string;
}
const Button = (props: ButtonProps) => {
return (
<button className="myClass">{value}</button>
)
}
This code is totally fine. However, what happens if you want to pass some native attributes to your button
like the onClick
method or the type
? You’ll surely add props to your interface
:
interface ButtonProps {
value: string;
type: string;
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
}
const Button = (props: ButtonProps) => {
const {value, onClick, type} = props;
return (
<button
onClick={onClick}
type={type}
className="primary">
{value}
</button>
);
}
Well, our code is still ok. Now, we also want to pass our classes dynamically to our component:
interface ButtonProps {
value: string;
type: string;
onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
className: string;
}
const Button = (props: ButtonProps) => {
const {value, onClick, type, className} = props;
return (
<button
onClick={onClick}
type={type}
className={className}>
{value}
</button>
);
}
const App = () => {
return (
<div>
<Button
type="button"
value="Submit"
onClick={(e) => {console.log("Clicked!"}}
className="primary"
/>
</div>
);
}
Do you see the problem with this approach? Each time we’ll need to use a native property of our button
tag, we will need to add a property to our interface to be able to overload it in our component.
Fortunately, we can change our interface to avoid these repetitions :
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
export default function Button(props: ButtonProps) {
return <button {...props}>{props.value}</button>;
}
In this way, you can pass all the previous props
to your component without adding every attribute in your interface
because it extends all the button tag native attributes :
<Button
value='Submit'
type='button'
onClick={(e) => {
console.log("submitted!");
}}
className='primary'
/>
Furthermore, a big advantage of this approach is that you can overload the attribute in your component in order to have more control :
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
export default function RadiusButton(props: ButtonProps) {
// Here we overload the className and add our own class to the button
return (
<button {...props} className={`${props.className border-radius}`} >
{props.value}
</button>
);
}
So next time you need to create a basic component with custom attributes, try to extend your component with native HTML Element attributes.
Posted on April 11, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.