0

I'm creating a bunch of components with some common functions inside, one of them is the one that handles rendering. Simplified, it looks like this:

const render = (
    instance: React.Component<{}, {flag: boolean}>,
    cb: () => React.ReactNode
) => instance.state.flag ? cb() : '';

class Test extends React.Component<{}, {flag: boolean}> {
    state = {flag: true};
    render() { return render(this, () => '') }
}

TypeScript, although, is not happy with this setup and says: 'render' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions. Personally, I can't see this reference.

As a possible clue, if I rewrite this code this way:

const render = (
    state: {flag: boolean},
    cb: () => React.ReactNode
) => state.flag ? cb() : '';

class Test extends React.Component<{}, {flag: boolean}> {
    state = {flag: true};
    render() { return render(this.state, () => '') }
}

...all works well, but I really want to use the component itself, due to some complexities left out of sight for now.

What's the problem? Can I do something with this common function so that TypeScript doesn't argue?


UPD: a bit of testing let me created the example which is a bit more close to the one needed:

type Component<T> = React.Component<{}, {flag: T}>
const render = <T>(
    instance: Component<T>,
    cb: () => React.ReactNode
) => instance.state.flag ? cb() : '';

class Test extends React.Component<{}, {flag: boolean}> {
    state = {flag: true};
    render() {
        const result = render(this, () => '');
        return result;
    }
    inner = () => 'test';
}

The main point here is the fact that external render function is generic, and instance type depends on the generic parameter, so that TypeScript is unable to substitute the proper parameter in place of T. Is it possible to help him without losing type-safety?

1 Answer 1

1

Problem here seems to be with type inference of the instance argument in the render function.

Let's see

  • Even if you mark the instance type as React.Component, when typescript try to infer render type it has to use 'this' instance that has to be React.Component<>, to do that it has to ckech render class member too, and back to start. So I suppose that typescript decide to give up about this inference
type RenderF = (
    instance: React.Component<any, any>,
    cb: () => React.ReactNode) => React.ReactNode;

const render: RenderF = (
    instance: React.Component<{}, {flag: boolean}>,
    cb: () => React.ReactNode
) => instance.state.flag ? cb() : null;

class Test extends React.Component<{}, {flag: boolean}> {
    state = {flag: true};
    render() { return render(this, () => null) }
}
  • So an easy solution is to mark RenderF instance arguments as any, to avoid typescript inference to check the instance again.
type RenderF = (
    instance: any, // <-----
    cb: () => React.ReactNode) => React.ReactNode;

const render: RenderF = (
    instance: React.Component<{}, {flag: boolean}>,
    cb: () => React.ReactNode
) => instance.state.flag ? cb() : null;

class Test extends React.Component<{}, {flag: boolean}> {
    state = {flag: true};
    render() { return render(this, () => null) }
}

UPD

If you don't want to loose type safety in instance argument you can specify only what you need in a new type

interface FlagComponent {
    state: { flag: boolean }
}

type RenderF = (
    instance: FlagComponent,
    cb: () => React.ReactNode) => React.ReactNode;

const render: RenderF = (
    instance: React.Component<{}, {flag: boolean}>,
    cb: () => React.ReactNode
) => instance.state.flag ? cb() : null;

class Test extends React.Component<{}, {flag: boolean}> {
    state = {flag: true};
    render() { return render(this, () => null) }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, the last one worked. I'd add that this looks like work for Pick<Component<T>, 'state'> or something like that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.