Specify specific props and accept general HTML props in Typescript React App
A co-worker of mine figured it out. Sharing here for broader visibility:
interface ComponentPropTypes = {
elementName?: keyof JSX.IntrinsicElements; // list of all native DOM components
...
}
// Function component
function Component({
elementName: Component = 'div',
...rest,
// React.HTMLAttributes<HTMLOrSVGElement>) provides all possible native DOM attributes
}: ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>)): JSX.Element {
return <Component {...rest} />;
}
// Class component
class Component extends React.Component<ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>> {
render() {
const {
elementName: Component,
...rest,
} = this.props;
return <Component {...rest} />
}
}
We can have a look at how div
props are defined:
interface IntrinsicElements {
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}
If we use React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
as the base type we will have all properties of div
. Since DetailedHTMLProps
just adds ref
to React.HTMLAttributes<HTMLDivElement>
we can use just this as the base interface to get all div
properties:
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper> // works now
}
Have a look at ComponentProps
, ComponentPropsWithRef
, and ComponentPropsWithoutRef
- this will accept a generic input that can be "div"
, "button"
, or any other component. It will include react specific props such as className
as well:
import React, {
forwardRef,
ComponentPropsWithoutRef,
ComponentProps,
ComponentPropsWithRef
} from "react";
const ExampleDivComponent = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<"div">
>(({ children, ...props }, ref) => {
return (
<div {...props} ref={ref}>
{children}
</div>
);
});
<ExampleDivComponent
className=""
style={{ background: "green" }}
tabIndex={0}
onTouchStart={() => alert("touched")}
/>;
const ExampleButtonComponent: React.FC<ComponentProps<"button">> = ({
children,
...props
}) => {
return <button {...props}>{children}</button>;
};
<ExampleButtonComponent onClick={() => alert("clicked")} />;
JSX.IntrinsicElements has this info, e.g.
const FooButton: React.FC<JSX.IntrinsicElements['button']> = props => (
<button {...props} className={`foo ${props.className}`} />
)
// alternative...
const FooButton: React.FC<React.PropsWithoutRef<
JSX.IntrinsicElements['button']
>> = props => <button {...props} className={`foo ${props.className}`} />
discovered this in the react-typescript-cheatsheet project.