aws-mobile-appsync-sdk-js
aws-mobile-appsync-sdk-js copied to clipboard
React-apollo 3.0 with aws-appsync-react
Do you want to request a feature or report a bug? Bug
What is the current behavior? Installing react-apollo 3.0 makes the Rehydrated component stop working. Going back to react-apollo 2.5.8 makes it work again
If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem.
Error message recieved: The context client is marked as required in Rehydrated, but its value is undefined.
What is the expected behavior? Rehydrated should have a client to be able to rehydrate
I am having the same issue!
My workaround is to create my own Rehydrated component that takes in client as a prop. Also i needed to add a resolution to the package.json otherwise it was crashing for another error.
"resolutions": { "apollo-client": "2.6.3" }
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import AWSAppSyncClient from 'aws-appsync';
const Rehydrate = props => (
<div
className={`awsappsync ${
props.rehydrated
? 'awsappsync--rehydrated'
: 'awsappsync--rehydrating'
}`}
>
{props.rehydrated ? props.children : <span>Loading...</span>}
</div>
);
class Rehydrated extends Component {
static propTypes = {
render: PropTypes.func,
children: PropTypes.node,
loading: PropTypes.node,
client: PropTypes.instanceOf(AWSAppSyncClient).isRequired,
};
constructor(props) {
super(props);
this.state = {
rehydrated: false,
};
}
async componentWillMount() {
await this.props.client.hydrated();
this.setState({
rehydrated: true,
});
}
render() {
const { render, children, loading } = this.props;
const { rehydrated } = this.state;
if (render) return render({ rehydrated });
if (children) {
if (loading) return rehydrated ? children : loading;
return <Rehydrate rehydrated={rehydrated}>{children}</Rehydrate>;
}
}
}
export default Rehydrated;
This is my custom Rehydrated component.
import React, { useContext, useEffect, useState } from 'react';
import { getApolloContext } from 'react-apollo';
import AWSAppSyncClient from 'aws-appsync';
const Rehydrated = ({ children }) => {
const { client } = useContext(getApolloContext());
const [rehydrated, setState] = useState(false);
useEffect(() => {
if (client instanceof AWSAppSyncClient) {
(async () => {
await client.hydrated();
setState(true);
})();
}
}, [client]);
return rehydrated ? <>{children}</> : null;
};
export default Rehydrated;
Basically the same, but in TypeScript. (might be better to have type guard.)
import { useApolloClient } from '@apollo/react-hooks';
const Rehydrated: React.FC = ({ children }) => {
const client = useApolloClient();
const [rehydrated, setRehydrated] = useState(false);
useEffect(() => {
(async () => {
await (client as AWSAppSyncClient<NormalizedCacheObject>).hydrated();
setRehydrated(true);
})();
}, [client]);
return (
<div className={`awsappsync ${rehydrated ? 'awsappsync--rehydrated' : 'awsappsync--rehydrating'}`}>
{rehydrated ? children : <span>Loading...</span>}
</div>
);
};
This is only a temporary solution. Mutations hang on the client currently.
It won't help as it seems AWSAppSyncClient diverged too far from ApolloClient for the rehydration to work. It'll only work w/react-apollo v3 if you disable offline entirely using disableOffline: true in client's options
@TeoTN - I disabled offline and still got the issue. I bumped to 3.0.1 wonder if that has anything to do w/ it?
@amcdnl well, I forgot to mention that I'm using the Rehydrate from @dai-shi AND disabling offline. I'm pretty sure this is a dumb solution (i.e. combining both, the Rehydrate is not needed probably when offline's off) but it temporarily does the thing.
@TeoTN - Hmm, still didn't work for me. I'll just hold out for the official update (fingers crossed).
@TeoTN - Hmm, still didn't work for me. I'll just hold out for the official update (fingers crossed).
I'm using @dai-shi 's Rehydrate and adding implicit any to my client and seems to work
<ApolloProvider client={client as any}>
<Rehydrated>
<Router />
</Rehydrated>
</ApolloProvider>
I tried this as well last weekend. I got past the rehydrate by writing a separate one but the mutations don't process. They get added to the redux-offline queue but do not execute. AppSync has an offline-link implemented which is defining the effect. I suspect there are things that needs to be sorted at that level. For mutations, I was unable to make it work without fundamentally changing AppSync core. I hope we will see an official adaptation of this soon.
Can AWS tell us what is the plan for supporting Apollo 3.0? Is it in the roadmap? Is there an ETA? @elorzafe @manueliglesias @dabit3
Heres the complete solution I ended up with:
In my package.json, I have:
{
"dependencies": {
"apollo-client": "^2.6.4",
"apollo-link": "^1.2.12",
"apollo-link-error": "^1.1.11",
"aws-amplify": "^1.1.36",
"aws-amplify-react": "^2.3.12",
"aws-appsync": "^1.8.1",
"react-apollo": "^3.0.1",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"aws-appsync-react": "^1.2.9",
},
"resolutions": {
"apollo-client": "2.6.3"
}
}
then I used one of the Rehydrates above which looks like:
import React, { useEffect, useState } from 'react';
import { useApolloClient } from 'react-apollo';
import AWSAppSyncClient from 'aws-appsync';
export const Rehydrated = ({ children }) => {
const client = useApolloClient();
const [rehydrated, setState] = useState(false);
useEffect(() => {
if (client instanceof AWSAppSyncClient) {
(async () => {
await client.hydrated();
setState(true);
})();
}
}, [client]);
return rehydrated ? children : null;
};
then my AWS config looks like:
import Amplify from 'aws-amplify';
import { Auth } from 'core/Auth';
import AWSAppSyncClient, { createAppSyncLink } from 'aws-appsync';
import { ApolloLink } from 'apollo-link';
import { onError } from 'apollo-link-error';
import { AWS_CUSTOM_CONFIG } from '../../aws-custom-exports';
Amplify.configure(AWS_CUSTOM_CONFIG);
const onErrorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.error(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) {
console.error(`[Network error]: ${networkError}`);
}
});
const auth = {
type: AWS_CUSTOM_CONFIG.aws_appsync_authenticationType as 'OPENID_CONNECT',
jwtToken: () => {
const session = Auth.getStoredSession();
if (session) {
return session.idToken;
}
}
};
export const appSyncClient = new AWSAppSyncClient(
{
url: AWS_CUSTOM_CONFIG.aws_appsync_graphqlEndpoint,
disableOffline: true,
region: AWS_CUSTOM_CONFIG.aws_appsync_region,
auth
},
{
link: ApolloLink.from([
onErrorLink,
createAppSyncLink({
url: AWS_CUSTOM_CONFIG.aws_appsync_graphqlEndpoint,
region: AWS_CUSTOM_CONFIG.aws_appsync_region,
auth,
complexObjectsCredentials: () => Auth.getStoredSession()
})
])
}
);
then I bring it all together in the index.tsx like:
import React, { Suspense, lazy } from 'react';
import ReactDOM from 'react-dom';
import { appSyncClient, Rehydrated } from 'core/Aws';
import { ApolloProvider } from 'react-apollo';
const Root = () => (
<ApolloProvider client={appSyncClient as any}>
<Rehydrated>
Hello
</Rehydrated>
</ApolloProvider>
);
ReactDOM.render(<Root />, document.getElementById('root'));
The current version of ApolloClient works nicely with this, even the apollo-boost one, with Cognito auth. Just want to get subscriptions on there instead "self-host" MQTT over WS.
let token = await Auth.currentSession().then(session => session.getIdToken().getJwtToken()) // Cognito
let client = new ApolloClient({ uri: awsconfig.aws_appsync_graphqlEndpoint, headers: { 'Authorization': token } }) // Same endpoint
I agree with consensus, latest Apollo Client [v3] now leads web development, and React Native.
We have now explored this issue further. The reason to use AppSync over just the ApolloClient is if you need the offline capability. We wrote our own Rehydrated and got around the issues there but then when posting a mutation, it got enqueued in redux-offline but never processed. We debugged the flow and found the issues to be in the offline-link created redux-offline effect.
AppSync is using a method called client.queryManager.buildOperationForLink that no longer exists to create the observable and hence nothing happens when redux-offline calls the effect.
We refactored this to the following construct using the same approach as Apollo does in the mutations:
client.queryManager.getObservableFromLink(
mutation,
extraContext,
variables,
false,
).subscribe({
next: data => {...
There were a few other minor issues that we have been changing to make the framework work for our specific use case but this is the key one the core framework falls over on.
We now have this working nicely with hooks and our adapted version of AppSync for our use case and I just wanted to share in case anyone else are looking to do the same or if this could inspire the core team to update the framework as it is a hinderance for adaptation.
We are also stuck here. We are using AppSync with Next.js. The solution found here works only client-side. The server-side cannot go beyond Loading... stage :-(
Hello everyone - I want to reply and assure you that we have not abandoned support of this project. Per my earlier comment, this is a complex process and to be completely transparent there are things in the Apollo library that are out of our control and there have also been breaking changes in Apollo versions which we're trying to account for, which is also why we pinned the version. We've been spending many hours trying to account for this but it's not simple to solve. After putting more thought into this we've got some initial solutions that I hope will unblock many of you. What we've done is packaged two of the "Links" which are part of the SDK - the Auth and Subscription Links - and published them to NPM. This will allow you to include them in the newer Apollo versions if you wish to use the newer features. This does not include the "Offline Link" for offline support which is more complex to solve and we're looking into for the future.
Installation of the links:
npm install aws-appsync-auth-link aws-appsync-subscription-link
Usage: https://github.com/awslabs/aws-mobile-appsync-sdk-js#using-authorization-and-subscription-links-with-apollo-client-no-offline-support
With is I would then give the following recommendations for choosing your client strategy:
- If you do not have offline use cases, you can either use the Auth & Subscription Links above with the latest Apollo client or alternatively use the Amplify GraphQL client instead of Apollo:https://aws-amplify.github.io/docs/js/api#amplify-graphql-client
- If you do have offline use cases please use the current version as-is with the older Apollo version. We're still working on a future strategy for the Offline Link.
Please let us know if you have any trouble using these links or if there is a scenario which we haven't accounted for here.
Thanks for giving us updates on this @undefobj . For offline support, what about the solution of @doldyn ?
Thanks for giving us updates on this @undefobj . For offline support, what about the solution of @doldyn ?
The problem is query manager uses some private methods, and when you factor in the new Local State features of Apollo which can override the cache there are several scenarios which can lead to inconsistent state of the underlying persisted data. Essentially you have a race condition of multiple "writers" which is why we need a deterministic mechanism to update the cache, otherwise we cannot guarantee data integrity.
@undefobj ,
In the aws-appsync code there is also another link added when constructing the client:
new link_1.ComplexObjectLink(complexObjectsCredentials)
When I saw it, I guessed that this is needed for "complex object" support for AppSync where it also involves working with connected files on S3. Is that right?
I'm not using this functionality ATM, but would this functionality still be working with the new proposed solution with new Apollo with included auth and sub. links?
@undefobj , In the aws-appsync code there is also another link added when constructing the client:
new link_1.ComplexObjectLink(complexObjectsCredentials)When I saw it, I guessed that this is needed for "complex object" support for AppSync where it also involves working with connected files on S3. Is that right? I'm not using this functionality ATM, but would this functionality still be working with the new proposed solution with new Apollo with included auth and sub. links?
Right now this is a separate link which is not published and still requires the older Apollo version. If there is high demand we can investigate publishing this as well, but I'd rather see if the core issue can be resolved.
Thanks to https://github.com/awslabs/aws-mobile-appsync-sdk-js/issues/448#issuecomment-541229659, it's now much cleaner with apollo-client. (without offline use cases)
Some notes:
-
a minor typo
apsync. -
it shows an error:
You are calling concat on a terminating link, which will have no effect. Modifying the code from the doc
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createSubscriptionHandshakeLink(url),
createHttpLink({ uri: url }),
]);
to
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createSubscriptionHandshakeLink(url),
]);
the error message is gone.
I think this is fine because of
https://github.com/awslabs/aws-mobile-appsync-sdk-js/blob/50185bb0ff97191eae38e76d869552aad6ce09ad/packages/aws-appsync-subscription-link/src/index.ts#L33
and it has resultsFetcherLink = createHttpLink({ uri: url })) as default.
I wish somebody could confirm that.
@dai-shi thanks for pointing this out
Using Typescript with this example. Here:
const auth = {
type: AUTH_TYPE.AWS_IAM,
credentials: () => Auth.currentCredentials()
}
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
]);
auth gets:
Type '{ type: AUTH_TYPE; credentials: () => Promise<ICredentials>; }' is not assignable to type 'AuthOptions'.
Type '{ type: AUTH_TYPE; credentials: () => Promise<ICredentials>; }' is not assignable to type 'AuthOptionsIAM'.
Types of property 'type' are incompatible.
Type 'AUTH_TYPE' is not assignable to type '"AWS_IAM"'.ts(2322)
index.d.ts(5, 5): The expected type comes from property 'auth' which is declared here on type '{ url: string; region: string; auth: AuthOptions; }'
Putting as const after AWS_IAM might solve it.
Thank @dai-shi! Facing another issue using proposed solution with Next.js. I posted a separate issue here.
Hello everyone - I want to reply and assure you that we have not abandoned support of this project. Per my earlier comment, this is a complex process and to be completely transparent there are things in the Apollo library that are out of our control and there have also been breaking changes in Apollo versions which we're trying to account for, which is also why we pinned the version. We've been spending many hours trying to account for this but it's not simple to solve. After putting more thought into this we've got some initial solutions that I hope will unblock many of you. What we've done is packaged two of the "Links" which are part of the SDK - the Auth and Subscription Links - and published them to NPM. This will allow you to include them in the newer Apollo versions if you wish to use the newer features. This does not include the "Offline Link" for offline support which is more complex to solve and we're looking into for the future.
Installation of the links:
npm install aws-appsync-auth-link aws-appsync-subscription-linkUsage: https://github.com/awslabs/aws-mobile-appsync-sdk-js#using-authorization-and-subscription-links-with-apollo-client-no-offline-support
With is I would then give the following recommendations for choosing your client strategy:
- If you do not have offline use cases, you can either use the Auth & Subscription Links above with the latest Apollo client or alternatively use the Amplify GraphQL client instead of Apollo:https://aws-amplify.github.io/docs/js/api#amplify-graphql-client
- If you do have offline use cases please use the current version as-is with the older Apollo version. We're still working on a future strategy for the Offline Link.
Please let us know if you have any trouble using these links or if there is a scenario which we haven't accounted for here.
@undefobj, does this solution works for SSR with IAM auth type? So far, I could not make it to work. I am setting it in the following way:
const auth = {
type: AUTH_TYPE.AWS_IAM as const,
credentials: () => Auth.currentCredentials()
}
const link = ApolloLink.from([
createAuthLink({ url, region, auth }),
createHttpLink({ uri: url })
]);
and in the CloudWatch I see this error:
Request Headers: {x-amzn-ErrorType=[UnauthorizedException]}
Client-side rendering works as expected.
@sakhmedbayev I can't see why it wouldn't, nothing specific here is different with a static build. What I have seen in the past is that when people host apps and put an extra DNS layer in front of AppSync with IAM the signature will get stripped since it contains region information.
This solution doesn't seem to work with any other auth strategy other than API Key.
This solution doesn't seem to work with any other auth strategy other than API Key.
We've tested with React apps, however maybe this could be something with SSR. That's not something that has been tested.