Amplify-js: GraphQLResult and Observable<object> incorrect types for API.graphql

Created on 25 Oct 2019  Â·  26Comments  Â·  Source: aws-amplify/amplify-js

Type 'GraphQLResult | Observable<object>' is not assignable to type '{ data: CreateDocumentMutation; }'.
  Type 'GraphQLResult' is not assignable to type '{ data: CreateDocumentMutation; }'.
    Types of property 'data' are incompatible.
      Type 'object | undefined' is not assignable to type 'CreateDocumentMutation'.
        Type 'undefined' is not assignable to type 'CreateDocumentMutation'.  TS2322

Getting the above error using typescript after the new Amplify js version.

It would be better to use Observable<any> in this case because using type object is incompatible. In TypeScript the type object is the opposite of any and is not assignable to anything.

GraphQL feature-request

Most helpful comment

This problem popped up for me during the React Amplify tutorial https://docs.amplify.aws/start/getting-started/data-model/q/integration/react#connect-frontend-to-api

Line 23-24:

const todoData = await API.graphql(graphqlOperation(listTodos))
const todos = todoData.data.listTodos.items

The error is:

Property 'data' does not exist on type 'GraphQLResult<object> | Observable<object>'.
  Property 'data' does not exist on type 'Observable<object>'.ts(2339)

I managed to go around this problem by using this obvious kludge:

const todoData: any = await API.graphql(graphqlOperation(listTodos))

It would be nice with a proper solution though ;)

All 26 comments

@selipso, can you share your code sample where you are getting this error?

async deleteDocument(id: string): Promise<{data:DeleteDocumentMutation}> {
    return API.graphql(graphqlOperation(deleteDocument, { input: { id } }));
}

Fairly simple wrapper function around the graphQL operation. The mutation deleteDocument and type DeleteDocumentMutation are auto-generated from the CLI using TypeScript. The data returned from the backend matches the type declarations but TypeScript complains.

Agreed, I don't see how Observable applies here. API.graphql() should return a GraphQLResult only. And if I typecast it to GraphQLResult, I still have issues:

image

Also, this:

export interface GraphQLResult {
    data?: object;
    errors?: [object];
    extensions?: {
        [key: string]: any;
    };
}

I would probably change the data line to:

data?: Record<string, any>;

@ffxsam It's complaining about result.data.createFolder because result.data is optional. Once TypeScript 3.7 is release this will be much easier

return result.data?.createFolder;

There's a PR to add support for generics to the API's which helps with some of the stuff mentioned here. See #3748

There's been much discussion about the return type of API.graphql(). Basically it's a GraphQLResult if you're performing a query/mutation and an Observable if you're performing a subscription

@buggy That's actually not true, as you can see above I'm checking for falsey values for result and result.data (which is partially blocked by the popup message).

It still gives me that error. And yeah, I haven't started using subscriptions yet, so your comment about Observable makes sense to me now.

@ffxsam I couldn't see the code behind the popup :(

The PR that I mentioned above should solve your problem once it's merged. It uses generics allowing your define the structure of the variables passed and data returned.

Something like:

API.graphql<{createFolder: SOMETYPE}>(...)

Would allow you to access

result.data?.createFolder

Thanks for the feedback and updates. Because of the convenient wrapper provided by API.graphql I am very happy with type ‘any’ and referring to the documentation for the data structure.

The generics solution is also fine or even Promise<{data:any}> & Observable<{data:any}>

I ended up having a deep dive into intersection types and union types because of this issue and believe the above format is appropriate considering Angular users who are also used to observables.

I'm seeing a similar issue with await API.graphql(). When trying to access data from the returned result:

TS2339: Property 'data' does not exist on type 'GraphQLResult | Observable<object>'.
  Property 'data' does not exist on type 'Observable<object>'.

we've solved this by adding an 'as unknown'.
If you have a better way to type, do not hesitate to share.

    (API.graphql(graphqlOperation(createAttributeMutation, { input: payload })) as unknown) as {
        data: CreateAttributeMutation
    }

I would use TS function overloading to achieve smarter return values so we don't have to bother with typecasting:

See example here, and mouse over result1 and result2 to see that the types are different.

I realize this is a breaking change due to the parameters, but IMO this is better DX (developer experience). @selipso @mlabieniec

any updates? seems like I always get a Promise and not Observable. It is a little annoying

Here's how I did it!

API.graphql() declaration:

... graphql<T>({ query: paramQuery, variables, authMode }: GraphQLOptions): Promise<GraphQLResult<T>> | Observable<T> & { [key: string]: any }; ...

GraphQLResult declaration:

... export interface GraphQLResult<T> { data?: T; errors?: [object]; extensions?: { [key: string]: any; }; } ...

What was changed:

  1. Added graphql<T> for more flexible response data type.
  2. I unioned Observable<T> with { [key: string]: any } declaration to the .graphql<T>(...) return type instead of adding { data?: any } which Observable will never have.

This problem popped up for me during the React Amplify tutorial https://docs.amplify.aws/start/getting-started/data-model/q/integration/react#connect-frontend-to-api

Line 23-24:

const todoData = await API.graphql(graphqlOperation(listTodos))
const todos = todoData.data.listTodos.items

The error is:

Property 'data' does not exist on type 'GraphQLResult<object> | Observable<object>'.
  Property 'data' does not exist on type 'Observable<object>'.ts(2339)

I managed to go around this problem by using this obvious kludge:

const todoData: any = await API.graphql(graphqlOperation(listTodos))

It would be nice with a proper solution though ;)

^ This is the workaround I've been using too

@michalmikolajczyk @selipso as you know using any disables type checking.

Until #5208 or #3748 get through, try something like this:

import { GraphQLResult } from "@aws-amplify/api";
import { ListTodosQuery } from "./api";

// ...

const todoData = (
  await API.graphql(graphqlOperation(listTodos))
) as GraphQLResult<ListTodosQuery>;

const todos = todoData.data.listTodos.items

@michalmikolajczyk @selipso as you know using any disables type checking.

Until #5208 or #3748 get through, try something like this:

import { GraphQLResult } from "@aws-amplify/api";
import { ListTodosQuery } from "./api";

// ...

const todoData = (
  await API.graphql(graphqlOperation(listTodos))
) as GraphQLResult<ListTodosQuery>;

const todos = todoData.data.listTodos.items

This results in todoData possibly 'undefined'

@michalmikolajczyk @selipso as you know using any disables type checking.
Until #5208 or #3748 get through, try something like this:

import { GraphQLResult } from "@aws-amplify/api";
import { ListTodosQuery } from "./api";

// ...

const todoData = (
  await API.graphql(graphqlOperation(listTodos))
) as GraphQLResult<ListTodosQuery>;

const todos = todoData.data.listTodos.items

This results in todoData possibly 'undefined'

Probably because you're using strict: true in your tsconfig.json. Try something like:

const todos = todoData?.data?.listTodos?.items

But be aware that items may be null, or even null[], (check the generated type definition). You have to handle this, in any way makes sense for your project, to keep type consistency.

I'm also trying to follow along with https://docs.amplify.aws/start/getting-started/data-model/q/integration/react-native#connect-frontend-to-api with Typescript by trying to add a new screen inside of https://github.com/ethanneff/react-native-web-typescript.

My issue is with the type to useState

src/screens/Amplify/index.tsx:33:18 - error TS2345: Argument of type '({ __typename: "Todo"; id: string; name: string; description: string | null; createdAt: string; updatedAt: string; } | null)[]' is not assignable to parameter of type 'SetStateAction<never[]>'.
  Type '({ __typename: "Todo"; id: string; name: string; description: string | null; createdAt: string; updatedAt: string; } | null)[]' is not assignable to type 'never[]'.
    Type '{ __typename: "Todo"; id: string; name: string; description: string | null; createdAt: string; updatedAt: string; } | null' is not assignable to type 'never'.
      Type 'null' is not assignable to type 'never'.

33         setTodos(todos);

I've solved it by for now by passing <any[]> and putting a guard around setTodos but that doesn't feel quite right here...

  const [todos, setTodos] = useState<any[]>([]); //TODO: Fix the type here

...

      if (todos) {
        setTodos(todos);
      }

Any experts with a better fix?

It's because need using Observable not from rxjs, but zen-observable-ts

im getting this do any one have solution for this

"Property 'data' does not exist on type 'GraphQLResult | Observable'.\n Property 'data' does not exist on type 'Observable'.",
code:
if(userInfo) {
// Check if user already exists in database
const userData = await API.graphql(graphqlOperation(getUser, {id: userInfo.attributes.sub}));
console.log (userData)
if (!userData.data.getUser) {
const user = {
id : userInfo.attributes.sub,
username : userInfo.username,
name : userInfo.username,
email : userInfo.attributes.email,
image : getRandomImage(),
}
await saveUserToDB(user);
} else {
console.log('user already exists');
}
}

@mauerbac I fail to see how this is a feature request. The codegen feature shouldn't produce code that needs to be manually modified/overridden for it to work..

Changing the imports in the generated api.service.ts from zen-observable to zen-observable-ts does fix the errors for me, though.

i add any: and it fixed my problem

Le ven. 27 nov. 2020 à 21:39, Sean van Mulligen notifications@github.com
a écrit :

@mauerbac https://github.com/mauerbac I fail to see how this is a
feature request. The codegen feature shouldn't produce code that needs to
be manually modified/overridden for it to work..

Changing the imports in the generated api.service.ts from zen-observable
to zen-observable-ts does fix the errors for me, though.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/aws-amplify/amplify-js/issues/4257#issuecomment-734984641,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ARSWRDSHE4ERADFSJVSTIRDSSAE67ANCNFSM4JE6JE2Q
.

but i still have this problem do you know how to solve it

Le ven. 27 nov. 2020 à 21:39, Sean van Mulligen notifications@github.com
a écrit :

@mauerbac https://github.com/mauerbac I fail to see how this is a
feature request. The codegen feature shouldn't produce code that needs to
be manually modified/overridden for it to work..

Changing the imports in the generated api.service.ts from zen-observable
to zen-observable-ts does fix the errors for me, though.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/aws-amplify/amplify-js/issues/4257#issuecomment-734984641,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ARSWRDSHE4ERADFSJVSTIRDSSAE67ANCNFSM4JE6JE2Q
.

any updates on it?
I don't like to have any type in code

I ran into something like this with @connection .. it doesn't write out the connected data types only a subset.

type Asset @model
    @key(name: "assetTitleIndex", fields: ["title"], queryField: "assetsByTitle")
    @key(name: "assetFolderIndex", fields: ["folder", "owner"], queryField: "assetsByFolder")
    @key(name: "assetOwnerIndex", fields: ["owner", "folder"], queryField: "assetsByOwner")
{
    id: ID!
    owner: ID!
    attempts: [Attempt] @connection(keyName: "attemptByAssetIndex", fields: ["id", "owner"])
    folder: String!
    title: String!
    description: String
    size: Int!
    manifest: AWSJSON!
    version: String!
    public: Boolean
    editors: [ID]
    createdAt: AWSDateTime
    updatedAt: AWSDateTime
}


type Attempt @model
    @key(name: "attemptByOwnerIndex", fields: ["owner"], queryField: "attemptByOwner")
    @key(name: "attemptByAssetIndex", fields: ["asset", "owner"], queryField: "attemptByAsset")
{
    id: ID!
    owner: ID!
    asset: ID!
    satisfied: Boolean!
    progress: Boolean!
    completion: Boolean
    score: Int!
    data: AWSJSON!
    attempts: Int!
    exitType: String!
    createdAt: AWSDateTime
    updatedAt: AWSDateTime
}

Results in a API.service.ts:

  OnCreateAssetListener: Observable<
    SubscriptionResponse<OnCreateAssetSubscription>
  > = API.graphql(
    graphqlOperation(
      `subscription OnCreateAsset {
        onCreateAsset {
          __typename
          id
          owner
          attempts {
            __typename
            // THIS PART IS MISSING
            items {
              __typename
              id
              owner
              asset
              satisfied
              progress
              completion
              score
              data
              attempts
              exitType
              createdAt
              updatedAt
            }
            // PART MISSING
            nextToken
          }
          folder
          title
          description
          size
          manifest
          version
          public
          editors
          createdAt
          updatedAt
        }
      }`
    )
  ) as Observable<SubscriptionResponse<OnCreateAssetSubscription>>;

And any section that needs it. I manually put it back in but if I touch the schema it wipes it out again.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TheRealRed7 picture TheRealRed7  Â·  3Comments

epicfaace picture epicfaace  Â·  3Comments

romainquellec picture romainquellec  Â·  3Comments

benevolentprof picture benevolentprof  Â·  3Comments

rygo6 picture rygo6  Â·  3Comments