Douglas Moura

Douglas Moura

Software Engineer

Douglas Moura

Douglas Moura

I write about TypeScript, React and Node.js.

How to extend HTML elements with React

Published at:Published at:Updated at:

How to extend HTML elements with React

Most of the work needed to create custom HTML elements that fit the design system of your company resides styling and adding your own props. So, let's say you have to create a custom Button, that should receive a children prop and should have DOM access via ref. That's how you can do it:

import { forwardRef } from 'react';

type ButtonProps = {
  loading?: boolean; // custom prop
} & React.PropsWithChildren<React.ComponentPropsWithRef<'button'>>;

const Button: React.FC<ButtonProps> = forwardRef(
  ({ loading, children, ...props }, ref) => {
    return (
      <button data-loading={loading} {...props} ref={ref}>
        {children}
      </button>
    );
  }
);

export default Button;

We use the PropsWithChildren generic interface that gives the children prop and receive React.ComponentPropsWithRef<'button'>, that passes all props that a button can receive.

Of course, you can change the interface ComponentPropsWithRef for ComponentPropsWithoutRef and drop the forwardRef function on the definition of your component (although, I do not recomend it - refs may be useful later on your application):

type ButtonProps = {
  loading?: boolean; // custom prop
} & React.PropsWithChildren<React.ComponentPropsWithoutRef<'button'>>;

const Button: React.FC<ButtonProps> = ({ loading, children, ...props }) => {
  return (
    <button data-loading={loading} {...props} ref={ref}>
      {children}
    </button>
  );
};

export default Button;

You may, even, drop the interface PropsWithChildren, but on doing that, you'd have to implement the children prop by yourself:

type ButtonProps = {
  loading?: boolean; // custom prop
  children?: React.ReactNode;
} & React.ComponentPropsWithoutRef<'button'>;

const Button: React.FC<ButtonProps> = ({ loading, children, ...props }) => {
  return (
    <button data-loading={loading} {...props} ref={ref}>
      {children}
    </button>
  );
};

export default Button;

Want more? Check the live implementation on StackBlitz

Leave a Reply

Loading comments...