Search code examples
reactjstypescripttypeerror

TypeScript: Expected type comes from property 'children' which is declared on 'IntrinsicAttributes & Prop'


I was working on a component that takes in a children prop that takes in an img element + another prop. But when I go on to use the component and pass all the props, I receive the following error:

Type 'Element' has no properties in common with type 'Pick<ImgHTMLAttributes, "src" | "alt">'.ts(2559)

App.tsx(5, 3): The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & Props'

Here is an example of my code (Link to CodeSandbox):

type Props = {
  children: Pick<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">
  otherProp?: any
}

const Image = ({children, otherProp}: Props) => {
  return (
    <picture>
      ...
      {children}
    </picture>
  )
}

I honestly don't understand what IntrinsicAttributes is, I can see that it is extending my Prop with this other type IntrinsicAttributes but not sure why exactly. And also the errors seem to go away when I remove otherProp. An explanation would be really helpful. Thanks a lot.


Solution

  • The type ImgHTTMLAttributes<HTMLImageElement> is a type that describes a simple object, that contains all properties that img jsx element can accept. Pick<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt"> is just a type { src: string; alt: string }. Note, that it does not describe the actual img jsx element, it just describes props that it potentially has.

    In typescript there is no way to say "this component only accepts an img jsx element that only has props alt and src" so there is no way to type this, at least not like you show in the sandbox. What you should probably do instead is get rid of children in the first place, and just pass src and alt straight to the Image component

    interface Props {
      src: string
      alt: string
      otherProp?: any;
    }
    
    const Image = ({ src, alt, otherProp }: Props) => {
      return <picture><img src={src} alt={alt} /></picture>;
    };
    
    const el = <Image otherProp="" src="cool" alt="Image alt tag" />
    
    

    Or if you don't want to write type definitions for src and alt yourself, you could take the existing types from ImgHTTMLAttributes like you did in your example, but it should look like this:

    type Props = Pick<ImgHTMLAttributes<HTMLImageElement>, "src" | "alt"> & {
      otherProp?: any;
    }
    
    const Image = ({ src, alt, otherProp }: Props) => {
      return <picture><img src={src} alt={alt} /></picture>;
    };
    
    const el = <Image otherProp="" src="cool" alt="Image alt tag" />
    
    

    Speaking about IntrinsicAttributes, this is a type that describes props that all components can receive, even if you didn't provide them in your props definitions. They are usually used internally by the library, this is why they are called intrinsic. For example, if you are using react, the only intrinsic property is key: you can attach it to any component without defining its types anywhere, and it is used internally by react. When you use any jsx component, its prop types are descibed by the type IntrinsicAttributes & PropsThathYouDefinedForYourComponent, in your case, IntrinsicAttributes & Props. This way, when you use this jsx component, you can pass both your custom props and internal library props, and the type checking still works