DEV Community

Yacine C
Yacine C

Posted on

React - Create a custom component from native HTML element with typescript

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>
    )
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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'
/>
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

So next time you need to create a basic component with custom attributes, try to extend your component with native HTML Element attributes.

Top comments (0)