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