Aws-mobile-appsync-sdk-js: Local state story

Created on 23 Jan 2018  路  18Comments  路  Source: awslabs/aws-mobile-appsync-sdk-js

Hi I'm a total noob to GraphQL, so I apologize if this is a stupid question, but I'm wondering what the story is for handling local state. I'm used to Redux, which I see is a dependency. However, I've been led to understand that with Apollo 2 comes a discouragement of using Redux alongside for state management, preferring to use https://github.com/apollographql/apollo-link-state. I see #3 exists, the solution to which may be the solution to this issue as well. However, I presume the authors of this library have already considered how users should manage local state, so I'm wondering what the recommended method is. I believe the cache is not to be used for local state, but I could be wrong.

Most helpful comment

Once PR #96 gets merged, it should be possible to use apollo-link-state with the AWS AppSync client like this:

import { ApolloLink } from 'apollo-link';
import { withClientState } from 'apollo-link-state';
import AWSAppSyncClient, { createAppSyncLink, createLinkWithCache } from "aws-appsync";

const stateLink = createLinkWithCache(cache => withClientState({
  cache,
  resolvers: {
    Mutation: {
      updateNetworkStatus: (_, { isConnected }, { cache }) => {
        const data = {
          networkStatus: {
            __typename: 'NetworkStatus',
            isConnected
          },
        };
        cache.writeData({ data });
        return null
      },
    },
  },
  defaults: {
    networkStatus: {
      __typename: 'NetworkStatus',
      isConnected: false
    }
  }
}));

const appSyncLink = createAppSyncLink({
  url: appSyncConfig.graphqlEndpoint,
  region: appSyncConfig.region,
  auth: {
    type: appSyncConfig.authenticationType,
    apiKey: appSyncConfig.apiKey
  }
});

const link = ApolloLink.from([stateLink, appSyncLink]);

const client = new AWSAppSyncClient({}, { link });

const result = await client.mutate({
  mutation: gql`mutation updateNetworkStatus($isConnected: Boolean) {
    updateNetworkStatus(isConnected: $isConnected) @client {
      isConnected
    }
  }`,
  variables: { isConnected: true }
});

console.log(result);

All 18 comments

Hi @rivertam - We don't have any specific recommendation as to how you want to manage your application state, it's up to you as the developer. You could use Redux, Vuex, or even simply setState() in React. While we use Redux Offline as a dependency, that is simply an SDK implementation (the goal is to handle offline persistence and synchronization as well as web socket connections via subscriptions) which you shouldn't base your application layer choices on as it could change based on future requirements.

Further to this question, is there anyway to go about disabling the behaviour of caching state to redux? It seems as if in the default configuration, ala:

const apolloClient = new AWSAppSyncClient({ url: appSyncConfig.graphqlEndpoint, apiUrl: appSyncConfig.graphqlEndpoint, region: appSyncConfig.region, auth: { type: appSyncConfig.authenticationType, apiKey: appSyncConfig.apiKey, }});

Keys get created in localStorage, reduxPersist:appsync and reduxPersist:offline that makes me believe that there is at least some default behaviour of trying to use redux for offline caching. Can I turn this off somehow? I am trying to use with Vue and am not using redux, and for now would like to completely disable offline caching and later perhaps use other methods for offline caching.

@nicokruger Have you tried using fetchPolicy: 'network-only'?

Really interested in using apollo-link-state is that possible?

On the newest version you can use disableOffline: true to turn this off.

Also interested in knowing if we can setup our own links somehow, but if you just want to turn off offline caching altogether disableOffline: true works for now.

Once PR #96 gets merged, it should be possible to use apollo-link-state with the AWS AppSync client like this:

import { ApolloLink } from 'apollo-link';
import { withClientState } from 'apollo-link-state';
import AWSAppSyncClient, { createAppSyncLink, createLinkWithCache } from "aws-appsync";

const stateLink = createLinkWithCache(cache => withClientState({
  cache,
  resolvers: {
    Mutation: {
      updateNetworkStatus: (_, { isConnected }, { cache }) => {
        const data = {
          networkStatus: {
            __typename: 'NetworkStatus',
            isConnected
          },
        };
        cache.writeData({ data });
        return null
      },
    },
  },
  defaults: {
    networkStatus: {
      __typename: 'NetworkStatus',
      isConnected: false
    }
  }
}));

const appSyncLink = createAppSyncLink({
  url: appSyncConfig.graphqlEndpoint,
  region: appSyncConfig.region,
  auth: {
    type: appSyncConfig.authenticationType,
    apiKey: appSyncConfig.apiKey
  }
});

const link = ApolloLink.from([stateLink, appSyncLink]);

const client = new AWSAppSyncClient({}, { link });

const result = await client.mutate({
  mutation: gql`mutation updateNetworkStatus($isConnected: Boolean) {
    updateNetworkStatus(isConnected: $isConnected) @client {
      isConnected
    }
  }`,
  variables: { isConnected: true }
});

console.log(result);

Hmm, do these two links use the same cache implicitly somehow??

Ok, I think I see here that it's as simple as creating and passing in the same cache:

https://github.com/awslabs/aws-mobile-appsync-sdk-js/pull/96#issuecomment-382470519

Hey @jpstrikesback

TL;DR

Yeah, in the sample I posted I am using the same cache for both links, although is not very clear.


Long version

To use apollo-link-state you need to use withClientState(), which in turns expects the cache to be passed to it. Now, this is not straightforward in this client, let me elaborate...

When you want to use the offline capabilities of the AppSync client, the client uses its own cache implementation and link chain; it has some internal logic to wait for the cache hydration from the offline storage before processing requests. This means that our cache is initialized asynchronously by the client itself and thus not available for link creation purposes before instantiation of the client.

To give the possibility of creating links that use the cache, a createLinkWithCache helper function is provided. (It will give you access to the same cache instance used internally by the AppSync client and link created by createAppSyncLink)

Its usage looks like this:

const linkThatUsesCache = createLinkWithCache((cache) => new MyLink(cache));

You don't need to pass a cache explicitly to the AWSAppSyncClient constructor (unless you REALLY want to, we don't support offline capabilities when using other cache implementations)

I hope this helps 馃槂

@manueliglesias Is it not required to declare the cache before using it. Such as const cache = new InMemoryCache();

@manueliglesias does this mean i can set headers like this ? [thinking of not using cognito and setting/reading JWT from Lambda function etc] will this clash with any of the auth stuff you guys set?

  const authMiddleware = new ApolloLink((operation, forward) => {
    operation.setContext({
      headers: {
        authorization: JWTtoken
      }
    })

    return forward(operation)
  })

//etc
const link = ApolloLink.from([authMiddleware, appSyncLink]);

@farzd I tried your code and I don't seem to be able to set custom headers. Did you figure out how? @manueliglesias
PS I am using AppSync with IAM auth. I really need to just be able to pass the user pool jwt in the headers, but AppSync w/ IAM auth doesn't provide the user pool info.

@honkskillet i'm asking a question, not providing a solution. Sorry.

@farzd Yeah. I tried you code and some other similar approaches with composing apollo-link and I haven't been able to get custom headers back to my appSync resolvers. I suspect AppSync is stripping them out?

@honkskillet it already attaches a header link: https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/master/packages/aws-appsync/src/client.js#L51

so not sure what happens if you add it again

Thanks

@honkskillet @farzd did you guys find a way to pass the jwt in the header? I'm not interested in cognito or any of the other auth settings provided by appsync.

@fgiarritiello i haven't looked since unfortunately, let me know if you find something.

I know it's been a while, but for anyone who stumbles here; This is super hacky, but works (in nodejs):

const Fetch = require('node-fetch')

let token = 'this is my jwt token'
global.fetch = (url, opts) => {
  opts.headers['jwt'] = token
  return Fetch(url, opts)
}
Was this page helpful?
0 / 5 - 0 ratings