awslabs / aws-mobile-appsync-sdk-js

JavaScript library files for Offline, Sync, Sigv4. includes support for React Native
Apache License 2.0
921 stars 266 forks source link

Network error: Can't find <queryName> on object (ROOT_QUERY) undefined #102

Open Jun711 opened 6 years ago

Jun711 commented 6 years ago
Error: Network error: Can't find **graphqlQueryName** on object (ROOT_QUERY) undefined.

It works most of the time but fails about 20% of the time.

Similar to these issues https://github.com/apollographql/react-apollo/issues/1932 https://github.com/apollographql/apollo-client/issues/3311 https://github.com/apollographql/apollo-client/issues/1701

this.hc().then(client => {
      return client.query({
        query: queryName
        fetchPolicy: network-only
      });
}).catch(err => {
      console.log('appsyncQuery err:', err);
});
this.hc().then(client => {
      const observable = client.watchQuery({
          query: queryName
          fetchPolicy: network-only
      });

      return observable.result();
}).catch(err => {
      console.log('appsyncQuery err:', err);
});

I set up appsync.service similar to this example https://github.com/aws-samples/aws-mobile-appsync-chat-starter-angular/blob/master/src/app/chat-app/appsync.service.ts

private newClient(session) {
    return new AWSAppSyncClient({
      url: appSyncConfig.graphqlEndpoint,
      region: appSyncConfig.region,
      auth: {
        type: appSyncConfig.authenticationType,
        jwtToken: session.idToken.jwtToken
      }
    }, {
      defaultOptions: {
        watchQuery: {
          fetchPolicy: 'network-only',
        }
      }
    });
  }

using "aws-appsync": "^1.0.15",

manueliglesias commented 6 years ago

Hi @Jun711

This behaviour might be caused by trying to access the cache before it is rehydrated from offline storage. Can you try this change in the file you linked (https://github.com/aws-samples/aws-mobile-appsync-chat-starter-angular/blob/master/src/app/chat-app/appsync.service.ts)?

  hydratedClient() {
    return Auth.currentSession().then(session => {
      return new Promise((resolve, reject) => {
        if (session) {
          this._client = this._client || this.newClient(session);
-          resolve(this._client.hydrated());
+ 
+          this._client.hydrated().then(resolve);
        } else {
          reject('No session');
        }
      });
    });
  }
ghost commented 6 years ago

I doesn't work even after hydrated. The only time this works for me is if I set the disableOffline option to true

smakman commented 6 years ago

I'm having the exact same issue as @eightbits-us in Safari Mobile. Setting disableOffline was the only thing that worked for me too.

mellson commented 6 years ago

@smakman thanks - this fixed it for me as well. However I would like to be able to use the offline capabilities in Safari Mobile, since this was one of the deciding factors in choosing AWS AppSync.

Jun711 commented 6 years ago

it worked for me using disableOffline option. Not sure about @manueliglesias suggestion as I didn't try. We decided not to use appsync and go for API Gateway for now. It seems that appsync doesn't work on older browsers. Is that right?

johnatCB commented 6 years ago

Any update on that issue @manueliglesias? Seems to happen only on Safari ...

tavo1987 commented 6 years ago

I'm having the same issue in Chrome, Can someone solve it without disableOffline option.?

elorzafe commented 6 years ago

@johnatCB @tavo1987 did you try the latest version (1.4.0) ?

@tavo1987 which Chrome version are you using? Is your app the first thing is opened on the browser?

luccamordente commented 5 years ago

I'm having the same issue here. Happens to me when I first open my app in a private tab on desktop and mobile Safari and also on mobile Chrome.

elorzafe commented 5 years ago

@luccamordente how did you configuring and using the client?

stephankaag commented 5 years ago

Glad that i'm not the only one.. :-) Any ideas yet?

ericlink commented 5 years ago

We are

@smakman thanks - this fixed it for me as well. However I would like to be able to use the offline capabilities in Safari Mobile, since this was one of the deciding factors in choosing AWS AppSync.

Same here, key capability for our app. Is there any current activity on this issue?

brandomorrill commented 5 years ago

This issue seems to be related to inconsistencies with state.offline.online in redux-offline. The following code from /src/link/offline-link.ts sometimes incorrectly reports offline when my application is initially loaded:

const { offline: { online } } = this.store.getState();

Unfortunately, waiting for the store to be hydrated does not solve the issue. We will need to disable offline in our application until this bug has been resolved.

ericlink commented 5 years ago

@manueliglesias is there anything we can do to help with this issue?

shrugs commented 5 years ago

Confirmed ~20% failure cases after clearing offline localstorage on latest stable Chrome desktop. 0% failure with disableOffline: true.

danielbayerlein commented 5 years ago

Is there a solution to this problem without having to forego offline functionality?

shrugs commented 5 years ago

I updated all of the dependencies to the versions least likely to break the build and that seems to have fixed this specific problem: https://github.com/awslabs/aws-mobile-appsync-sdk-js/compare/master...XLNT:master lmk if that works for you as well, @danielbayerlein

patilkun commented 5 years ago

@shrugs does above solution work on iOS device? I am using angular 6 with Ionic 4. Below is the code. This works on Chrome desktop browser. But when I run Ionic app on iOS device then I get below error intermittently. Sometimes it works but, sometimes I get this error.

ERROR: Unhandled error Network error: Can't find field listDecks on object undefined. ApolloError@ionic://localhost/vendor-es2015.js:136963:32
ionic://localhost/vendor-es2015.js:138087:60
step@ionic://localhost/vendor-es2015.js:184175:27
ionic://localhost/vendor-es2015.js:184149:75
ZoneAwarePromise@ionic://localhost/polyfills-es2015.js:3882:37
__awaiter@ionic://localhost/vendor-es2015.js:184145:36
ionic://localhost/vendor-es2015.js:138530:25
forEach@[native code]
ionic://localhost/vendor-es2015.js:138529:25
forEach@[native code]
broadcastQueries@ionic://localhost/vendor-es2015.js:138524:29
ionic://localhost/vendor-es2015.js:138030:63
onInvoke@ionic://localhost/vendor-es2015.js:71868:39
run@ionic://localhost/polyfills-es2015.js:3130:49
ionic://localhost/polyfills-es2015.js:3861:39
onInvokeTask@ionic://localhost/vendor-es2015.js:71849:43
runTask@ionic://localhost/polyfills-es2015.js:3174:57
drainMicroTaskQueue@ionic://localhost/polyfills-es2015.js:3565:42
invokeTask@ionic://localhost/polyfills-es2015.js:3475:40
timer@ionic://localhost/polyfills-es2015.js:5656:34

Service that will initialize and setup AWSAppSyncClient.

import { Injectable } from '@angular/core';
import AWSAppSyncClient from '@xlnt/aws-appsync';
import aws_exports from '../aws-exports';
import { resolve } from 'q';
import { Storage } from '@ionic/storage';
import * as localForage from "localforage";
@Injectable()
export class AppsyncService {
   _hc;

   constructor(public storage: Storage) {
    this.initializeClient();
   }

   initializeClient(){
    const client = new AWSAppSyncClient({
      url: aws_exports.aws_appsync_graphqlEndpoint,
      region: aws_exports.aws_project_region,
      auth: {
        type: aws_exports.aws_appsync_authenticationType,
        apiKey: aws_exports.aws_appsync_apiKey,
      },
      offlineConfig: {
       storage: localForage,
       callback: (err, succ) => {
         if(err) {
           const { mutation, variables } = err;
           alert(err);
           console.log(`ERROR for ${mutation}`, err);
         } else {
           const { mutation, variables } = succ;
           alert("SUCCESS");
           alert(succ);
           console.log(`SUCCESS for ${mutation}`, succ);
         }
       },
     },
     disableOffline: false
    });
    this._hc = client;
   }

   hc() {
    let promise: Promise<any> = new Promise((resolve: any) => {
        this._hc.hydrated().then(resolve);
    });
    return promise;
   }
}

Component that uses above service


import { Component,OnInit } from '@angular/core';
import { AppsyncService } from '../appsync.service';

import gql from 'graphql-tag';

const listDeckNames = gql`  
query listDeckNames {  
  listDecks {
    items {
      id
      name
    }
  }
}
`;

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {
  decks ;
  questions;
  constructor(private appsync: AppsyncService) {
  }

  ngOnInit() {
    this.getDecks();
  }

  getDecks() {
    this.appsync.hc().then(client => {
      try {
      client.watchQuery({
        query: listDeckNames,
        fetchPolicy:'cache-and-network',
      }).subscribe(({data}) => {
        if (data) { 
          console.log("data ",data);
          this.decks = data.listDecks.items;
          console.log("deckNames : ", data.listDecks.items);
        }
      })
      ,(err) => {
        alert(err);
      };
    } catch (error) {
      alert(error);
    }
    });
  }
}
shrugs commented 5 years ago

@patilkun not certain, sorry — my only target is browsers. check the diff for what's changed but imo seems like the problem is somewhere in the specific apollo version that I'd updated to

patilkun commented 5 years ago

Thanks @shrugs

krsnaa commented 5 years ago

seeing this issue 100% of the time with AppSync in nodeJS, - https://aws-amplify.github.io/docs/js/api#aws-appsync-sdk.

setting disableOffline to true just makes the program hang.

AymericG commented 4 years ago

How did you guys solve this issue without disabling offline?

sammartinez commented 4 years ago

@Jun711 The sample has been archived, are you still looking for a sample with this?

mindnuts commented 4 years ago

Can we open another issue for this? With a recent attempt i could not get it working without adding the disableOffline: true config value.

mikeRChambers610 commented 4 years ago

UPDATE This article covers the update. Offline capabilities from appsync are being swept under the rug while they push Datastore as the new option.

Summary: The aws-appsync package has a dependency on apollo-client@2.4.6. This setup won't allow using @apollo/react-hooks which only functions properly with react-apollo@2.6. AWS is now pushing DataStore as the new method of offline capabilities.

Link https://dev.to/willsamu/how-to-get-aws-appsync-running-with-offline-support-and-react-hooks-678

vendexis-stan commented 4 years ago

Traced the first issue (of unknown total issues) to be caused by store.ts, which uses @redux-offline/redux-offline's default detectNetwork function, which tries to register windows event (https://github.com/redux-offline/redux-offline/blob/9e84f38be0720a069d47487eb0717b00ada11843/src/defaults/detectNetwork.js) - this causes the link to always use offline strategy (even at initialization)

There's an example in redux-offline's issue log that shows an example for how the detectNetwork function can be modified for Node.js (or web workers) https://github.com/piranna/redux-offline-Node.js-PoC/blob/master/index.js Most likely need to modify store.ts's createStore function that initializes offline with the detectNetwork function described above...

I think there are still more of these similar situation needing to be flushed out, but @mikeRChambers610 's solution looks a lot more flexible and promising than patching on this project (thanks for that!). Just leaving the info here for anyone interested in continuing this path :)

thadeu commented 3 years ago

what resolved for me was this

export const client = new AWSAppSyncClient({
  ....
  auth: {
    type: AppSyncConfig.authenticationType,
    apiKey: AppSyncConfig.apiKey
  },
  // dont use optimize response
  disableOffline: true
})
rpmadden08 commented 3 years ago

The aws-appsync library ultimately uses the following code to set the boolean that's used to determine if the device is online or not (from @redux-offline/redux-offline/lib/defaults/detectNetwork.js):

if (window.requestAnimationFrame) {
    window.requestAnimationFrame(function () {
      return callback({
        online: online
      });
    });
} else {
    setTimeout(function () {
      return callback({
        online: online
      });
    }, 0);
}

The problem this creates is that it typically ends up waiting for the first animation frame before the online boolean gets set. In some devices/environments (notably Cordova/Ionic ios, likely others), the initial batch of appsync queries will trigger before the first animation frame (but after the rehydrated value has been set to true) so the online boolean is still null, causing the query to behave as if it's offline.

To work around this issue, you can wait a couple of animation frames before rendering the component that will make the initial appsync queries. A functional react component might have something like this:

const [isPassedFirstAnimationFrame, setIsPassedFirstAnimationFrame] = useState(false);

useEffect(() => {
    window.requestAnimationFrame(() => {
      // One requestAnimationFrame call may or may not be enough - it depends on whether the detectNetwork's function fires first or second. We call it again here just to be safe.
      window.requestAnimationFrame(() => {
        setIsPassedFirstAnimationFrame(true);
      });
    });
}, []);

return (
    {isPassedFirstAnimationFrame &&
        <ComponentThatMakesAppsyncQueries/>
    }
    {!isPassedFirstAnimationFrame &&
        <LoadingComponent/>
    }
);

Ideally, a similar hook would be integrated into the Rehydrated component of aws-appsync's library so waiting for rehydration would be sufficient.

Mughees605 commented 3 years ago

any update on this issue.?