Reputation: 25803
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
Upvotes: 26
Views: 32491
Reputation: 161
my solution for this case, it works with Typescript Generics
QueryWrapper.ts
import { QueryResult } from "@apollo/client/react/types/types";
interface Props<TData, TVariables> {
query: QueryResult<TData, TVariables>;
children: (data: TData) => any;
}
export function QueryWrapper<TData, TVariables>({ query, children }: Props<TData, TVariables>) {
if (query.loading) {
return "loading...";
}
if (query.error) {
return query.error.message;
}
if (!query.data) {
return "Data is empty"
}
return children(query.data as TData);
}
usage:
const recipeQuery = useQuery<{ recipes: Recipe[] }>(GET_RECIPES);
<QueryWrapper query={recipeQuery}>
{({recipes}) => (
JSON.stringify(recipes)
)}
</QueryWrapper>
Upvotes: 0
Reputation: 515
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.
Upvotes: 1
Reputation: 141
for me this command solved the problem:
rm -rf node_modules && rm yarn.lock && yarn
Upvotes: -5
Reputation: 796
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
}
Upvotes: 3
Reputation: 4067
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>;
};
Upvotes: 35