22

I have a React component called HandleQuery that handles the results of an Apollo GraphQL query:

import React, { ReactNode } from 'react';

import { CenteredMessage } from './centered-message';

export interface HandleQueryProps {
    loading: boolean,
    error?: Error,
    children: ReactNode
}

export const HandleQuery = ({ loading, error, children }: HandleQueryProps) => {
    if (loading) {
        return <CenteredMessage>Loading...</CenteredMessage>;
    }

    if (error) {
        return <CenteredMessage>{error.message}</CenteredMessage>;
    }

    return children;
};

When this component is used in another component it does not compile:

import React from 'react';

import { useQuery } from 'react-apollo-hooks';
import gql from 'graphql-tag';
import { HandleQuery } from '..';
import { MutationType } from '../../graphql-types';
import { AuthorsPanel } from './authors-panel';
import { GET_AUTHORS } from './authors-queries';
import { AuthorMutated } from './__generated__/AuthorMutated';

export class AuthorsContainer extends React.Component {
    render() {
        const { loading, error, data } = useQuery(GET_AUTHORS);
        return (
            <!-- THIS LINE PRODUCES A COMPILER ERROR -->
            <HandleQuery loading={loading} error={error}>
                <AuthorsPanel data={data} />
            </HandleQuery>
        );
    }
}

The use of HandleQuery above produces the following error:

Type error: JSX element type '{} | null | undefined' is not a constructor function for JSX elements. Type 'undefined' is not assignable to type 'Element | null'. TS2605

What does this mean and how can I get rid of it?

Update

Converting AuthorsContainer to a function component does not eliminate the error:

export const AuthorsContainer = () => {
    const { loading, error, data } = useQuery(GET_AUTHORS);
    return (
        <HandleQuery loading={loading} error={error}>
            <AuthorsPanel data={data} />
        </HandleQuery>
    );
};

Update 2 Implemented suggestion from @FredyC:

import React from 'react';

import { CenteredMessage } from './centered-message';

export interface HandleQueryProps {
    loading: boolean;
    error?: Error;
}

export const HandleQuery: React.FC<HandleQueryProps> = ({
    loading,
    error,
    children
}) => {
    if (loading) {
        return <CenteredMessage>Loading...</CenteredMessage>;
    }

    if (error) {
        return <CenteredMessage>{error.message}</CenteredMessage>;
    }

    return children;
};

Now the error on the container component has gone away, but a new compiler error has popped up on the HandleQuery component:

Type error: Type '({ loading, error, children }: PropsWithChildren<HandleQueryProps>) => {} | null | undefined' is not assignable to type 'FunctionComponent<HandleQueryProps>'.
  Type '{} | null | undefined' is not assignable to type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)> | null'.
    Type 'undefined' is not assignable to type 'ReactElement<any, string | ((props: any) => ReactElement<any, string | ... | (new (props: any) => Component<any, any, any>)> | null) | (new (props: any) => Component<any, any, any>)> | null'.  TS2322
Naresh
  • 18,757
  • 25
  • 99
  • 162
  • Start `console.log()`ing everything. Find out what is null. From what it seems like `HandleQuery` is undefined. Perhaps you meant `import { HandleQuery } from '../'`? – Goodbye StackExchange Feb 27 '19 at 12:22
  • @FrankerZ - This is a *compile-time* error (TSnnnn is a TypeScript compiler error code), not a runtime error. (The OP also conveniently said "this line produces a compiler error" although in his case, he used all caps.) – T.J. Crowder Feb 27 '19 at 12:23
  • Can hooks be used in classes? I thought they were functional component only? – rrd Feb 27 '19 at 12:27
  • HandleQuery is imported correctly. It is defined in ../index.tsx – Naresh Feb 27 '19 at 12:37
  • Just read the Hooks FAQ. It says "You can’t use Hooks inside of a class component". Let me try to convert that to a function component. – Naresh Feb 27 '19 at 12:46
  • Converting AuthorsContainer to a function component does not eliminate the error. See my update. – Naresh Feb 27 '19 at 13:02

4 Answers4

31

There is a long term issue regarding ReactNode. I don't understand specifics, but it's something hard-wired in TypeScript itself and is being worked on.

Either way, a solution should be easy. Don't use ReactNode with functional components :) The children prop is implicitly included in React.FC type.

The same problem goes with returning children. It can be either overcome by wrapping into <React.Fragment> or <div> if you prefer, but since it's just a type error, you can convince TypeScript that you know what you are doing :)

import React, { ReactNode } from 'react';

import { CenteredMessage } from './centered-message';

export interface HandleQueryProps {
    loading: boolean,
    error?: Error,
}

export const HandleQuery: React.FC<HandleQueryProps> = ({ loading, error, children }) => {
    if (loading) {
        return <CenteredMessage>Loading...</CenteredMessage>;
    }

    if (error) {
        return <CenteredMessage>{error.message}</CenteredMessage>;
    }

    return children as ReactElement<any>;
};
FredyC
  • 3,339
  • 2
  • 26
  • 36
  • Hi Fredy, now the error on the container component has gone away, but a new compiler error has popped up on the HandleQuery component. Please see my update 2. – Naresh Feb 27 '19 at 16:49
  • I think it would be best if you create codesandbox.io to demonstrate the issue. It's hard to guess what's wrong without seeing a whole picture. No need to set up Apollo there, of course, you can simulate that. – FredyC Feb 27 '19 at 18:42
  • Was difficult to demo using codesandbox.io because it uses a different version of TypeScript. Here's a very simple repo that shows the issue: https://github.com/nareshbhatia/simple-react – Naresh Feb 28 '19 at 04:29
  • Ah right, fixed the answer. Focus on the last code line and 3rd paragraph for explanation. – FredyC Feb 28 '19 at 09:59
  • Wow, I would have never thought about that! Thanks, so much. – Naresh Feb 28 '19 at 13:42
  • I had the same issue and changed my objects to use the last syntax you posted. It worked fine for almost everything, except a component with type parameters. Any way to use FunctionalComponent there? – Xerus Jul 30 '19 at 15:31
  • Generics are for its own question, but check this ... https://spectrum.chat/react/general/typescript-generic-functional-components~9951d01e-a94e-435c-97f9-099208a00436 – FredyC Jul 30 '19 at 19:09
  • This is still a problem, so I don't think we can any longer say it's being worked on. – Patrick Sep 24 '20 at 19:16
3

At this case, it can be more easy simply define children of type ReactElement if I am not obviating something.

export interface HandleQueryProps {
    loading: boolean,
    error?: ApolloError,
    children: ReactElement
}
xeladejo
  • 560
  • 4
  • 15
0

Switching ReactNode to JSX.Element resolves the type issue.

According to the related github issue on React, using JSX.Element instead of ReactNode is suggested for functional components.

You can check the details of JSX.Element vs ReactNode in typescript-cheatsheets/react page.

Ryan Rho
  • 475
  • 5
  • 18
-3

for me this command solved the problem:

rm -rf node_modules && rm yarn.lock && yarn

SwissCodeMen
  • 2,397
  • 3
  • 12
  • 21