microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.26k stars 12.52k forks source link

Pipe function + `React.memo` returns component with `any` props #38930

Open OliverJAsh opened 4 years ago

OliverJAsh commented 4 years ago

TypeScript Version: 3.9.2

Search Terms: generic pipe pipeWith react memo HOC props any

Code

import * as React from 'react';

declare function pipeWith<A, B>(a: A, ab: (a: A) => B): B;

type Props = { foo: number };
declare const MyComponent: React.FunctionComponent<Props>;

// βœ… correct props type
// React.NamedExoticComponent<Props>
const r1 = React.memo(MyComponent);

// ❌ `any` props type
// React.MemoExoticComponent<React.ComponentType<any>>
const r2 = pipeWith(MyComponent, React.memo);

// Workaround
// βœ… correct props type
// React.NamedExoticComponent<Props>
const r3 = pipeWith(MyComponent, (C) => React.memo(C));

Using latest version of @types/react (at the time of writing: 16.9.35).

Expected behavior:

See above.

Actual behavior:

See above.

Playground Link:

https://stackblitz.com/edit/react-ts-4ih6jn

Related Issues:

https://github.com/microsoft/TypeScript/issues/25637, although that was closed as a duplicate of an issue which has since been closed (https://github.com/microsoft/TypeScript/issues/10957), so I decided to post a new issue.

RyanCavanaugh commented 4 years ago

What's the definition of memo ?

OliverJAsh commented 4 years ago

https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e3313c33a10b59c1515074d045d31fd463e79098/types/react/index.d.ts#L847-L854

RyanCavanaugh commented 4 years ago

If someone can provide an isolated repro we can investigate

dragomirtitian commented 4 years ago

@RyanCavanaugh Hope this helps:

// The presence of any overloads confuses inference, leave just one of them in and everything works
declare function memo<T>(Component: T): T;
declare function memo<T>(Component: T): T;

declare function pipeWith<A, B>(a: A, ab: (a: A) => B): B;

type Props = { foo: number };
declare const MyComponent: Props;

// βœ… correct props type
// Props
const r1 = memo(MyComponent);

// ❌ `unknown` props type
// unknown
const r2 = pipeWith(MyComponent, memo);

// Workaround
// βœ… correct props type
// Props
const r3 = pipeWith(MyComponent, (C) => memo(C));

Playground Link

Just the presence of any overloads will confuse the inference on the original use case. Leave just the one signature and everything works as expected.

lifeiscontent commented 3 years ago

@RyanCavanaugh here's a reproduction

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAKjgQwM5wEoFNkGN4BmUEIcARFDvmQNwBQdMAnmFnAArKUB2M7xYVAB4AasgA2AVywA+OAF44Abzpw4AEywFkk8TDFSsAfgBccA9PpqAbhOmnzdrFbgRuAYQAWybgHMsZgAUtoZmFlgAlApy1hDA6i64nsDi6jwmqmpwAD6YVDAAdNh4MACi4lggWLxCXinq-BCCok4yMplqucX4RfnlldUwtcmpjc3hbQDaALr0AL70dASS3PjAbhxcgwCS4OIthjKBYAKoZpw8fKcH0jJRKmqUMJJQ3HCBHXBC6sDW7VlZJTdQp1VI8AogZBgY6nApJeo8AA073hqSi8n+AKyqPUBWYrAU8kUoPUnyxcCMeRKcPEbiw-SqvECOORD3J7KSWFwAGssOozCcmqgCiFpITFDiCoLBCKnIiyey1G4vD5-EEsNZBmZgQUVX56ZqagAJAAqAFkADLbbhgSRlCqMmB3aLKBWKtTAAjvDWDOEvS4mrj+Qqcnl8+5u91qaXC5XefXMka4mOywwRFxRgFzSPkubyzNwOYRHNwMzcXTiBURbMAoQAeh+f0y6bo2bouDcqHgF0GCipPSqIAggR7vF2YHELcYLDYJLGwgmfbZcdVxm1+V18f8pUNMCNPnUFSgQlNluttvtA14MkSni5vPUDgARhAIBUfC5RQFHIZ5otlqsMDrG8JLjvsEwwkKZhztcEwRo8WDPK87yfEIwA2naCqhg+8hKCm2F8jWWJfrhADKMBQOhviQTKX7VgqK76rhKaMf4RFqHWmItm2HbcF2cAkn2OqDsOoF7FOdZ1nAkioFRcAwHecAduAdK8OgADuwAKRAdr9oUIlwOp0DcugBDoVgDAAWsGylAAHsgexYIE8FKZ28CTF+yKoIh4TTEJG4yVg5HIDATkAIwtghSFvB8tajvAJFKF+cyuB4W5YLh3n6E4cyYrWgmJQADClnEKsM9RwIlYUlXlWTlaklVOLhABMNWoXW8VcfMdBAA

OliverJAsh commented 3 years ago

I'm running into this all of the time, not just when using React.memo.

Example using Object.values:

declare function pipe<A, B>(a: A, ab: (a: A) => B): B;

// Type of `value1` is `any[]`, expected `(string | number)[]`
const value1 = pipe({ foo: 1, bar: 'abc' }, Object.values);
// Workaround:
const value2 = pipe({ foo: 1, bar: 'abc' }, (x) => Object.values(x));

Playground here.

Here's another example when using RxJS v7 (7.0.0). I've reduced the test case down further in this playground.

import { lastValueFrom, of } from 'rxjs';
import { tap } from 'rxjs/operators';

declare function pipe<A, B>(a: A, ab: (a: A) => B): B;

//
// `of` example
//

{
    // Type of `value1` is `Observable<never>`, expected `Observable<string>`
    const value1 = pipe('foo', of);
    // Workaround:
    const value2 = pipe('foo', (x) => of(x));
}

//
// `lastValueFrom` example
//

pipe(of('foo'), lastValueFrom).then((x) => {
    // Type of `x` is `unknown`, expected `string`
    x;
});
// Workaround:
pipe(of('foo'), (ob) => lastValueFrom(ob)).then((x) => {
    x;
});

//
// `tap` example
//

declare const hof: <T>(g: (t: T) => void) => (t: T) => void;

of('foo').pipe(
    tap(
        hof((x) => {
            // Type of `x` is `unknown`, expected `string`
            x;
        }),
    ),
);
// Workaround:
of('foo').pipe(
    tap((value) =>
        pipe(
            value,
            hof((x) => {
                x;
            }),
        ),
    ),
);