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.