'Fighing with TS2322 error about generic value asignment

I'm trying to create some higher-order react components that will have some default values for components "named". This is the simple implementation that I'd like to work (this is just an example to showcase the problem):

type SomeProps = {
  a: string
}
type Variants = 'variantA' | 'variantB'
const somePredefinedValues: Record<Variants, SomeProps> = {
  variantA: { a: 'a' },
  variantB: { a: 'b' },
}

export const withVariants = <Props extends SomeProps>(Component: React.ComponentType<Props>) => {
  return ({ variant, ...props }: Props & { variant?: Variants }) => {
    if (!variant) {
      return <Component {...props} />
    }
    return <Component {...somePredefinedValues[variant]} {...props} />
  }
}

This obvoiuslty result with TS2322 error saying that

Type 'Omit<Props & { variant?: Variants | undefined; }, "variant">' is not assignable to type 'IntrinsicAttributes & Props & { children?: ReactNode; }'.
  Type 'Omit<Props & { variant?: Variants | undefined; }, "variant">' is not assignable to type 'Props'.
    'Omit<Props & { variant?: Variants | undefined; }, "variant">' is assignable to the constraint of type 'Props', but 'Props' could be instantiated with a different subtype of constraint 'SomeProps'.

I tried many ways to get around this and fix the issue, like (a bit ugly IMO but should be correct):

type SomeProps = {
  a: string
}
type Variants = 'variantA' | 'variantB'
const somePredefinedValues: Record<Variants, SomeProps> = {
  variantA: { a: 'a' },
  variantB: { a: 'b' },
}

export const withVariants = <Props,>(
  Component: React.ComponentType<Omit<Omit<Props, keyof SomeProps>, 'variant'> & SomeProps>
) => {
  return ({
    variant,
    ...props
  }: Omit<Omit<Props, keyof SomeProps>, 'variant'> & SomeProps & { variant?: Variants }) => {
    const standardProps: Omit<Omit<Props, keyof SomeProps>, 'variant'> & SomeProps = props //here is the issue
    if (!variant) {
      return <Component {...standardProps} />
    }
    return <Component {...somePredefinedValues[variant]} {...standardProps} />
  }
}

Which resulted in:

Type 'Omit<Omit<Omit<Props, "a">, "variant"> & SomeProps & { variant?: Variants | undefined; }, "variant">' is not assignable to type 'Omit<Omit<Props, "a">, "variant"> & SomeProps'.
  Type 'Omit<Omit<Omit<Props, "a">, "variant"> & SomeProps & { variant?: Variants | undefined; }, "variant">' is not assignable to type 'Omit<Omit<Props, "a">, "variant">'.
    Type 'Exclude<Exclude<keyof Props, "a">, "variant">' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.
      Type 'Exclude<keyof Props, "a">' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.
        Type 'keyof Props' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.
          Type 'string | number | symbol' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.
            Type 'string | number | symbol' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.
              Type 'string | number | symbol' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.
                Type 'string' is not assignable to type '"a" | Exclude<Exclude<Exclude<keyof Props, "a">, "variant">, "variant">'.

Is there any way to achieve desired behaviour in TS?



Solution 1:[1]

The problem seems to be caused by Omit (applied implicitly in the first example by TS when destructuring with rest operator) losing the overall type information, as described here.

Just removing the destructuring seems to do the trick.

type SomeProps = {
  a: string
}
type Variants = 'variantA' | 'variantB'
const somePredefinedValues: Record<Variants, SomeProps> = {
  variantA: { a: 'a' },
  variantB: { a: 'b' },
}

export const withVariants = <Props extends SomeProps>(Component: React.ComponentType<Props>) => {
  return (props: Props & { variant?: Variants }) => {
    if (!props.variant) {
      return <Component {...props} />
    }
    return <Component {...somePredefinedValues[props.variant]} {...props} />
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Thiago Pereira Maia