typescriptmaterial-uitypescript-generics

How can I get correct component prop types in MUI v4 with forwardRef?


https://v4.mui.com/guides/typescript/#usage-of-component-prop shows this example of a generic custom component wrapping Typography:

function GenericCustomComponent<C extends React.ElementType>(
  props: TypographyProps<C, { component?: C }>,
) {
  /* ... */
}

which makes props of the component prop available in GenericCustomComponent:

function ThirdPartyComponent({ prop1 } : { prop1: string }) {
  return <div />
}
// ...
<GenericCustomComponent component={ThirdPartyComponent} prop1="some value" />;

This works fine.

However, in my case some wrapper components are defined using forwardRef. Simplified:

import { Link, LinkProps } from '@material-ui/core'

export const CustomLink = forwardRef<HTMLAnchorElement, LinkProps>(({ children, ...muiProps }, ref) => {
  return (
    <Link ref={ref} {...muiProps}>
      {children}
    </Link>
  )
})

and the type of CustomLink is

React.ForwardRefExoticComponent<Pick<LinkProps<"a", {}>, ...> & React.RefAttributes<...>>

Is it possible to make CustomLink generic similarly to GenericCustomComponent, so its type will be something like

<C extends React.ElementType = "a"> React.ForwardRefExoticComponent<Pick<LinkProps<C, { component?: C }>, ...> & React.RefAttributes<...>>

I don't believe the above type is actually legal in TypeScript, but the intention should hopefully be clear.


Solution

  • This is the version I ended up with which seems to work in the initial testing:

    import { Link, LinkProps } from '@material-ui/core'
    
    interface LinkType {
      <C extends React.ElementType = 'a'>(props: LinkProps<C> & { component?: C }): React.ReactElement
      defaultProps?: Partial<LinkProps>
      displayName?: string
    }
    
    // eslint-disable-next-line react/display-name -- false positive
    export const CustomLink = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
      const { children, ...muiProps } = props
    
      return (
        <Link ref={ref} {...muiProps}>
          {children}
        </Link>
      )
    }) as LinkType
    

    but I wasn't able to make it generic to avoid duplication for multiple components. Any improvements welcome.