Typescript React HOC with forwardRef typing problem

901 Views Asked by At

I need to specify the argument type and return type of the Higher-Order Component with forwardRef in React. Currently, I ended up with the implementation below. It works but not perfect since I haven't specified the exact ElementType. If I try to add an additional generic type Element extends React.ElementType this error occurs:

Error:(34, 10) TS2322: Type 'Pick<PropsWithChildren<withPermissionsProps & Props & ComponentProps<Element>>, "children" | Exclude<...> | Exclude<...>> & { ...; }' is not assignable to type 'IntrinsicAttributes & ComponentProps<Element> & Props & { children?: ReactNode; }'.
  Type 'Pick<PropsWithChildren<withPermissionsProps & Props & ComponentProps<Element>>, "children" | Exclude<...> | Exclude<...>> & { ...; }' is not assignable to type 'ComponentProps<Element>'.

If I hardcode ElementType for instance by 'div' I get the following error:

Error:(32, 10) TS2769: No overload matches this call.
  Overload 1 of 2, '(props: ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement> & Props, context?: any): ReactElement<...> | ... 1 more ... | null', gave the following error.
    Type 'Pick<PropsWithChildren<withPermissionsProps & Props & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>>, "slot" | ... 255 more ... | Exclude<...>> & { ...; }' is not assignable to type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement> & Props'.
      Type 'Pick<PropsWithChildren<withPermissionsProps & Props & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>>, "slot" | ... 255 more ... | Exclude<...>> & { ...; }' is not assignable to type 'Props'.
        'Props' could be instantiated with an arbitrary type which could be unrelated to 'Pick<PropsWithChildren<withPermissionsProps & Props & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>>, "slot" | ... 255 more ... | Exclude<...>> & { ...; }'.
  Overload 2 of 2, '(props: PropsWithChildren<ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement> & Props>, context?: any): ReactElement<...> | ... 1 more ... | null', gave the following error.
    Type 'Pick<PropsWithChildren<withPermissionsProps & Props & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>>, "slot" | ... 255 more ... | Exclude<...>> & { ...; }' is not assignable to type 'IntrinsicAttributes & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement> & Props & { ...; }'.
      Type 'Pick<PropsWithChildren<withPermissionsProps & Props & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>>, "slot" | ... 255 more ... | Exclude<...>> & { ...; }' is not assignable to type 'Props'.
        'Props' could be instantiated with an arbitrary type which could be unrelated to 'Pick<PropsWithChildren<withPermissionsProps & Props & ClassAttributes<HTMLDivElement> & HTMLAttributes<HTMLDivElement>>, "slot" | ... 255 more ... | Exclude<...>> & { ...; }'.
import PermissionDenied from '@/components/PermissionDenied'
import cn from 'classnames'
import React from 'react'

import style from './withPermissions.module.scss'

export interface withPermissionsProps {
  readable?: boolean
  executable?: boolean
  showPlaceholder?: boolean
}

export function withPermissions<Props>(
  Component: React.ComponentType<
    React.ComponentProps<React.ElementType> & Props
  >,
): React.ForwardRefExoticComponent<
  React.PropsWithoutRef<
    withPermissionsProps & Props & React.ComponentProps<React.ElementType>
  >
> {
  return React.forwardRef(function withPermissions_(
    {
      className,
      readable = false,
      executable = false,
      showPlaceholder = false,
      ...otherProps
    },
    ref,
  ) {
    return (
      ((readable || executable) && (
        <Component
          {...otherProps}
          ref={ref}
          className={cn(
            style.withPermissions,
            {
              [style.withPermissions_readonly]: readable && !executable,
              [style.withPermissions_executeOnly]: executable && !readable,
            },
            className,
          )}
        />
      )) ||
      (showPlaceholder && <PermissionDenied />) ||
      null
    )
  })
}

export default withPermissions

Also to make sure that intersection types don't contain the same keys I tried to omit them but it didn't help.

Maybe someone had this problem before, I think it is a common thing which must be made once then it shouldn't be a problem.

0

There are 0 best solutions below