TypeScript: Unexpected error when using generic with Pick and Exclude

TypeScript Version: 3.2.1

Search Terms: generic pick exclude

Code

//
// Example 1
//

const fn = <Params extends {}>(
    params: Pick<Params, Exclude<keyof Params, never>>,
): Params =>
    // Unexpected error
    /* Type 'Pick<Params, Exclude<keyof Params, never>>' is not assignable to type 'Params'. */
    params;

//
// Example 2
//

import * as React from 'react';
import { ComponentType, FunctionComponent } from 'react';

type User = { name: string };
type UserProp = { user: User };

export const myHoc = function<ComposedComponentProps extends UserProp>(
    ComposedComponent: ComponentType<ComposedComponentProps>,
) {
    // These two should be equivalent, but they're not?
    // Doesn't work:
    type Props = Pick<
        ComposedComponentProps,
        Exclude<keyof ComposedComponentProps, never>
    >;
    // Works:
    // type Props = ComposedComponentProps;

    const Component: FunctionComponent<Props> = props => (
        // Unexpected error
        /* Type 'PropsWithChildren<Pick<ComposedComponentProps, Exclude<keyof ComposedComponentProps, never>>>' is not assignable to type 'IntrinsicAttributes & ComposedComponentProps & { children?: ReactNode; }'.
            Type 'PropsWithChildren<Pick<ComposedComponentProps, Exclude<keyof ComposedComponentProps, never>>>' is not assignable to type 'ComposedComponentProps'. */
        <ComposedComponent {...props} />
    );

    return Component;
};

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 33
  • Comments: 21 (3 by maintainers)

Most upvoted comments

@aprilandjan You’re not alone 😃 I was following some examples and I just didn’t get it to work. I thought I was doing something wrong, but it broke with TypeScript v3.2.

I have found a workaround to “fix” this issue. It’s a fairly simple fix and you can apply it by spreading the properties as any instead of the typed component. You keep all type-checking on the outside, so it’s a fairly non-intrusive fix. Just change:

return (<WrappedComponent {...this.props} locale={locale} />);

into

return (<WrappedComponent {...(this.props as any)} locale={locale} />);

and the error is gone.

Same problem for me. Broke my typing:

export type Injector<P> = <T extends P>(Component: React.ComponentType<T>) => React.ComponentType<Subtract<T, P>>;
export type Subtract<T, K> = Omit<T, keyof K>;
export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

const withContext: Injector<Context> = (Component) => (props) => (
  <ContextProvider>{(context) => <Component {...context} {...props} />}</ContextProvider>
);

@OliverJAsh Isn’t that related to #28884?

Hi Guys. Heres my complete (simplified) example which worked with TS 3.1 but does not work with TS 3.2

import * as React from "react"

const AuthContext = React.createContext({})

export type Omit<T, K extends string> = Pick<T, Exclude<keyof T, K>>

export interface IAuthContext {
    currentUser: string
}

export interface IAuthContextProp {
    auth: IAuthContext
}

export function withAuthContext<
    TComponentProps extends IAuthContextProp,
    TWrapperProps = Omit<TComponentProps, keyof IAuthContextProp>
>(
    Component: React.ComponentType<TComponentProps>
): React.ComponentType<TWrapperProps> {
    return (props: TWrapperProps): React.ReactElement<TComponentProps> => (
        <AuthContext.Consumer>{(auth) => (
            <Component auth={auth} {...props} />
        )}</AuthContext.Consumer>
    )
}

The error in TS 3.2 (on the <Component auth= ... line) is:

Type '{ auth: {}; } & TWrapperProps' is not assignable to type 'IntrinsicAttributes & TComponentProps & { children?: ReactNode; }'.
  Type '{ auth: {}; } & TWrapperProps' is not assignable to type 'TComponentProps'. ts(2322)

Thanks for your workaround @ramondeklein , it works nicely. In my case I simply changed return (props: TWrapperProps) to return (props: any)

I wonder if this could be a problem with the React typings? For reference I am on "@types/react": "16.7.6"

Hmm, so it looks like fixing this was on the roadmap for TS 3.3, but then it got removed, and it was briefly on the milestone for 3.4 but got removed from that too, so I’m not sure when this will be addressed. 😞

That said, I ended up using the workaround proposed in https://github.com/Microsoft/TypeScript/issues/28748#issuecomment-450497274. While seeing props as any made me worried, as @ramondeklein mentioned, all type-checking is maintained on the outside interface to your code, so depending on the structure of your code, you might still be getting type safety where it matters most. In my case I also left a comment in the source linking to this issue so that readers know it’s a workaround for something that will hopefully eventually be fixed.

Is this the same problem? What we are doing has really nothing to do with my example here, but it is the simpler way to reproduce the same problem that I found.

interface ValueWrapper<V> {
    value: V;
}

interface StringValue extends ValueWrapper<string> {
    fontSize: number;
}

const foo: Exclude<StringValue, ValueWrapper<string>> = {
    // Type 'number' is not assignable to type 'never'. [2322]
    fontSize: 2
};

Used to work with TypeScript 3.1.1 and is preventing us from upgrading.

@sledorze just curious, did you find any satisfactory workarounds for this? Unfortunately it’s preventing us from upgrading to TS 3.2 at the moment

Same problems here too. Lost a day finding workarounds in our big monorepo…