Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
OliverJAsh opened this issue Jun 4, 2020 · 6 comments
Open

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

OliverJAsh opened this issue Jun 4, 2020 · 6 comments
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone

Comments

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Jun 4, 2020

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:

#25637, although that was closed as a duplicate of an issue which has since been closed (#10957), so I decided to post a new issue.

@RyanCavanaugh
Copy link
Member

What's the definition of memo ?

@RyanCavanaugh RyanCavanaugh added the Needs Investigation This issue needs a team member to investigate its status. label Jun 15, 2020
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jun 15, 2020
@RyanCavanaugh
Copy link
Member

If someone can provide an isolated repro we can investigate

@dragomirtitian
Copy link
Contributor

@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.

@RyanCavanaugh RyanCavanaugh added the Rescheduled This issue was previously scheduled to an earlier milestone label Dec 11, 2020
@OliverJAsh
Copy link
Contributor Author

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;
            }),
        ),
    ),
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs Investigation This issue needs a team member to investigate its status. Rescheduled This issue was previously scheduled to an earlier milestone
Projects
None yet
Development

No branches or pull requests

5 participants