Mimo
Mimo

Reputation: 6075

How to make Typescript recognize the type for an Apollo Query

I am trying to get Apollo to integrate with TypeScript. I have a React Class that looks like the following:

interface Data {
  allVendors: Array<VendorType>;
}

class AllVendorsQuery extends Query<Data> {}

const ShowVendors: React.SFC<> = props => {
  return (
    <AllVendorsQuery query={fetchVendors}>
      {({ loading, error, data: { allVendors } }) => {
        if (loading) {
          return 'Loading...';
        }
        if (error) {
          return `Error! ${error.message}`;
        }

        return (
          allVendors &&
          allVendors.map((vendor, index: number) => {
            return (
              <div key={`${vendor.name}_${index}`}>
                #<strong>{vendor.id}</strong>
                &nbsp;{vendor.name}
              </div>
            );
          })
        );
      }}
    </AllVendorsQuery>
  );
};

export default ShowVendors;

The query is:

export default gql`
  query GetVendors {
    allVendors {
      id
      name
    }
  }
`;

TypeScript is complaining about the fact that [ts] Type 'Data | undefined' has no property 'allVendors' and no string index signature. which occurs on this line: {({ loading, error, data: { allVendors } }).

However, if I restructure the code using apollo-connect rather than the Query component I don't get any complain from TypeScript:

import { graphql, compose, QueryResult } from 'react-apollo';

interface ShowVendorsProps {
  data: QueryResult & { allVendors?: VendorType[] };
}

class ShowVendors extends React.Component<ShowVendorsProps> {
  render() {
    const {
      data: { allVendors }
    } = this.props;

    if (allVendors && allVendors.length > 0) {
      return (
        <div>
          {allVendors.map((vendor, index: number) => {
            return (
              <div key={`${vendor.name}_${index}`}>
                #<strong>{vendor.id}</strong>
                &nbsp;{vendor.name}
              </div>
            );
          })}
        </div>
      );
    } else {
      return 'Loading';
    }
  }
}

export default compose(graphql(fetchVendors))(ShowVendors);

What's the difference between the two? How can I rewrite the type for the first statement?

Upvotes: 2

Views: 2863

Answers (2)

ardatan
ardatan

Reputation: 87

There is a small library and a CLI to generate TypeScript typings for both server (according to your schema) and client (according to your schema and GraphQL documents). It also generates resolvers signature and very customizable.

You can try it here: https://github.com/dotansimha/graphql-code-generator

And there is a blog post here about that package; https://medium.com/the-guild/graphql-code-generator-for-typescript-react-apollo-7b225672588f

The idea behind it was to allow the developer to get the most out of GraphQL and the generated typings, and making it easier to customize the generated output.

It can also generate react-apollo components with typings you need.

Upvotes: 3

Gerald Urschitz
Gerald Urschitz

Reputation: 11

Since you're destructuring data with data: { allVendors } in the first codeblock, TypeScript complains because data might be undefined, for instance when the data is still loading.

So for TS not complaining you could just destructure after the loading check, with a default value for allVendors, something like:

interface Data {
  allVendors: Array<VendorType>;
}

class AllVendorsQuery extends Query<Data> {}

const ShowVendors: React.SFC<> = props => {
  return (
    <AllVendorsQuery query={fetchVendors}>
      {({ loading, error, data }) => {
        if (loading) {
          return 'Loading...';
        }
        if (error) {
          return `Error! ${error.message}`;
        }

        // If data is not undefined, then it sets allVendors accordingly.
        // Otherwise it sets it to null (which you check for anyways below)             
        const {allVendors} = data || {allVendors: null}; 

        return (
          allVendors &&
          allVendors.map((vendor, index: number) => {
            return (
              <div key={`${vendor.name}_${index}`}>
                #<strong>{vendor.id}</strong>
                &nbsp;{vendor.name}
              </div>
            );
          })
        );
      }}
    </AllVendorsQuery>
  );
};

export default ShowVendors;

Upvotes: 1

Related Questions