2

I tried to create the following interfaces and types to see the difference:

interface Props1 extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {}
interface Props2 extends React.HTMLProps<HTMLDivElement> {}

type Dif1 = Omit<Props1, keyof Props2>;
type Dif2 = Omit<Props2, keyof Props1>;

It seems like Dif1 is {} but Dif2 has a lot more properties like allowFullScreen, allowTransparency, etc. But as far as I know, allowFullScreen and allowTransparency are attributes for iframe element. So how come it shows up in Props2 where I only want to extend attributes on div element? What are the use cases for Props1 and Props2 respectively?

chaonextdoor
  • 5,019
  • 15
  • 44
  • 61

1 Answers1

2

The short answer is that DetailedHTMLProps<E, T> only extends HTMLAttributes<T> but React.HTMLProps<T> extends AllHTMLAttributes<T>, which then itself extends HTMLAttributes<T>.

Use React.ComponentWithRef<"tag-here"> or React.ComponentWithoutRef<"tag-here"> when you want to have the narrowest set of HTML props for your component. Use React.HTMLProps<HTMLElement> when you want the widest.


What ends up happening is when you pass HTMLDivElement into the generic type parameter of HTMLProps<T>, you're first taking a stop at AllHTMLAttributes<T> before going through the inheritance hierarchy down through HTMLAttributes<T>, and eventually ending at DOMAttributes<T>. This gives you the kitchen sink of all HTML attributes, which makes sense. The only thing that the generic parameter HTMLDivElement is doing here is being drilled down into a ClassAttributes<T> interface that grabs the LegacyRef<T> for your component and also into the eventual DOMAttributes<T> interface that uses T to correctly type all of the event handlers like onClick, onDrag, onBlur, and the many others. Passing HTMLDivElement into HTMLProps<T> will do virtually nothing to narrow the type of props that are returned.

When you use DetailedHTMLProps<E extends HTMLAttributes<T>, T> however, based on what E is passed in, you will access the narrowest relevant interface. Note that when you choose the <a> tag, you're accessing a uniquely different interface than when you choose the <div> or <summary> tag, which both share React.HTMLAttributes<T>

interface DetailedHTMLProps<E extends HTMLAttributes<T>, T> = E & ClassAttributes<T>;

interface JSX.IntrinsicElements {
  a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
  // ... some distance down
  div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
  // ... some distance down
  summary: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
  // ...
}

As of React 17.0 (when I'm writing this), the calling code should use ComponentPropsWithRef<T extends ElementType> or ComponentPropsWithoutRef<T extends ElementType> and typescript should get the detailed props for you based on the type parameter passed in.

export type MyType = React.ComponentPropsWithoutRef<"div">;

// if we follow the type magic to its conclusion
React.ComponentPropsWithoutRef<"div"> = PropsWithoutRef<ComponentProps<"div">>;
ComponentProps<"div"> = JSX.IntrinsicElements["div"]
JSX.IntrinsicElements["div"] = React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
zprobinson
  • 43
  • 7