Closed beornborn closed 5 years ago
@beornborn hmm I would have expected that to work. However, does your query have a status
field? If not it may be stripped by apollo-client's store interactions on queries
@jbaxleyiii
Query doesn't have status
field.
Previously I added custom data with own wrapper like this
export function* apolloCallWrapper(callback: Function): Object {
try {
const response = yield call(callback)
console.log('response', response)
if (response.errors) {
return { status: 'Fail', error: response.errors.map(e => e.message).join(', ') }
} else {
return { status: 'Success', data: response.data }
}
} catch (err) {
const errorClasses = err.graphQLErrors.map(x => x.name)
const error = err.message.replace('GraphQL error: ', '')
if (errorClasses.includes('UnauthorizedError')) {
yield logout()
}
return { status: 'Fail', error }
}
}
and in saga
const result = yield apolloCallWrapper(() =>
apolloClient.query({query: someQuery}),
)
Can I achieve this with afterware? Or not because apollo-client will strip all my custom data?
Fields not asked for in the query will be stripped by the caching layer. Try this:
const s = response.data.person.name = new String(response.data.person.name);
s.extra = "data";
You can do the same thing with new Number()
.
@riking looks like a dirty hack.
Why should I put status about request in some person's name value?
Also I need universal solution to pass status for every request, and these requests have different keys in data
key.
If there is no obvious and simple way to modify response data at this point, I'd rather continue using apolloCallWrapper. It's simple, not much typing and I can do whatever I want there with no restrictions.
Hi all,
i have a question about extensions.
My server puts some additional metadata to the response and i can see it on the afterware. But it gets stripped underway.
Is it possible work around it without hacking?
Thanks
As suspected in https://github.com/apollographql/apollo-client/issues/2534#issuecomment-343951913, I've confirmed that the additional data being added to the response is being stripped by the cache. To verify this, set the default fetchPolicy
to no-cache
in the repro:
export const apolloClient = new ApolloClient({
link,
cache: cache.restore(window.__APOLLO_STATE__),
defaultOptions: {
query: {
fetchPolicy: 'no-cache',
},
},
});
The console will now show that saga
has the previously missing status
:
afterware {"data":{"person":{"name":"Luke Skywalker","__typename":"Person"},"status":"SUCCESS"}}
saga {"data":{"person":{"name":"Luke Skywalker","__typename":"Person"},"status":"SUCCESS"},"loading":false,"networkStatus":7,"stale":false}
We should probably allow any form of response
updates, or at least update the Afterware (data manipulation)
example we show in the docs to make this limitation clear. Thanks!
@hwillson Any idea of the performance hit for using 'no-cache' in the fetch policy? We've been using InMemoryCache
from apollo-cache-inmemory
.
This work around would allow me to add IDs set in the response header to the query results. The IDs are sent to our analytics service which will help us track down bugs in our microservices.
It seems like the other option is to add the IDs to every query/mutation so that the cache doesn't throw it out, which seems sub optimal.
@MicahRamirez I have this exact same use case. I've tried a ton of stuff and absolutely no joy! The best option I've thought of is to simply throw an Error, including my correlation ID in the error message - then catch the error at a higher level and parse out the ID using a regex. Not at all an ideal solution :(
@chucksellick Are you unable to set the fetch policy to no cache as shown above? That solution will work for me, for now. From hwillson's comments the current behavior is not intended, so the work around should be sufficient for the short term at least for us.
I am about to be on vacation for about 14 days, but when I get back @hwillson what kind of help are you looking for?
@beornborn Did you find a solution for this?
const networkLink = split(
({ query }) => {
const { kind, operation } = getMainDefinition(query);
return kind === 'OperationDefinition' && operation === 'subscription';
},
webSocketLink,
httpLink,
)
const parseResponse = new ApolloLink((operation, forward) => {
return forward(operation).map((response) => {
response.data.parsed = transformData(response.data)
return response
})
})
const link = parseResponse.concat(networkLink)
return new ApolloClient({
link: link,
cache: new InMemoryCache({ addTypename: true }),
defaultOptions: {
query: {
fetchPolicy: 'no-cache', // This was an attempt to fix this issue, but it didn't work
errorPolicy: 'all',
},
},
})
Somewhere between the afterware and the Query children component the parsed
field is removed
@hwillson the no-cache
didn't work for me :(
@rafaelsales I use workaround over graphql
const response = yield apolloCallWrapper(() =>
apolloClient.mutate({
mutation: UpdateMember,
variables: preparedFormData,
}),
)
const apolloCallWrapper = function* apolloCallWrapper(callback: Function): Object {
try {
const response = yield call(callback)
console.log('response', response)
if (response.errors) {
return { status: 'Fail', error: response.errors.map(e => e.message).join(', ') }
} else {
return { status: 'Success', data: response.data }
}
} catch (err) {
const errorInfo = `
${err.stack}
${JSON.stringify(err.graphQLErrors)}
`
bugsnagNotify('Response Error', errorInfo)
console.log(err.stack, err.graphQLErrors)
const errorClasses = err.graphQLErrors.map(x => x.name)
const error = err.message.replace('GraphQL error: ', '')
if (errorClasses.includes('UnauthorizedError')) {
yield logout()
}
return { status: 'Fail', error }
}
}
export default apolloCallWrapper
I'm experiencing this issue too, but I don't think it has to do with no-cache
.
FilterToSchema seems to be the source of my problems. Even the documentation says:
FilterToSchema: Given a schema and document, remove all fields, variables and fragments for types that don’t exist in that schema.
I applied my own transformResponse
transform but it seems that FilterToSchema
is applied after everything else because it still gets stripped away. (In my specific case, it seems that my call to mergeSchemas
calls delegateToSchema
which applies FilterToSchema
by default. I have no control over anything except one of the schemas being merged - I'm using addThirdPartySchema
with Gatsby)
I am not an expert on transforms, but perhaps the solution is to create a priority
mechanism to control the order of the transforms applied?
@jbaxleyiii Any thoughts on this?
Any progress on this?
I'm having the same issue here when using cache. I wanna handle pagination with the WooCommerce REST API like so:
const authRestLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers }) => ({
headers: {
...headers,
authorization: `Basic ${btoa(`${CLIENT_KEY}:${SECRET_KEY}`)}`,
},
}));
return forward(operation).map((result) => {
const { restResponses } = operation.getContext();
const wpRestResponse = restResponses.find(res => res.headers.has('x-wp-total'));
if (wpRestResponse) {
result.data.headers = {
xWpTotal: wpRestResponse.headers.get('x-wp-total'),
xWpTotalpages: wpRestResponse.headers.get('x-wp-totalpages'),
};
}
return result;
});
});
When I use no-cache
as fetch policy, it works fine. Otherwise the data.headers
key gets stripped out.
I have the same problem, I'm trying to append the field's types to the result, what I did at the end (this is NOT a solution but an hack) is to append a simple query like this:
__type(name: "User") {name}
and then replace data.__type with what I need using formatResponse
I think I have found a solution for this problem. At first, you have to define a local scheme. You can read more about it here.
For the provided case it should looks like this:
new ApolloClient({
link: addStatusLink.concat(authLink.concat(link)),
cache,
typeDefs: gql`
type LocalStatus {
id: String!
success: String!
}
`,
resolvers,
});
Then you have to modify your query:
const yourQuery = gql`
query someQueryWithStatus {
someRealDataQuery {
id
name
}
status @client {
id
success
}
}
`;
Finally, you have to modify your transformations:
const addStatusLink = new ApolloLink((operation, forward) => {
return forward(operation).map((response) => {
response.data.status = {
id: 1,
success: 'SUCCESS',
__typename: 'LocalStatus',
};
return response;
})
})
I tried this, it works for me with latest versions of packages:
"apollo-cache-inmemory": "^1.6.2",
"apollo-client": "^2.6.3",
"apollo-link": "^1.2.12",
Hope It'll help someone. I spent hours for this solution 🤓
@hwillson hi. Looks like it could be solved without any hacks.
Hi folks
After spending hours struggling with this I came to a solution that worked well for my needs (I'm integrating my React app to a WordPress API using apollo-link-rest
).
@cy6eria your solution doesn't work unless you set fetch-policy
to no-cache
. Otherwise, Apollo will return the headers from cache for every request, which is bad because if you're trying to use the response headers to do pagination, for example, it will mess up everything.
So, here is what I did:
1) Modify query to ask for result
and headers
:
const getPosts = gql`
query posts($maxPosts: String!, $page: Int!) {
posts(maxPosts: $maxPosts, page: $page) @rest(type: "PostsPayload", path: "/wp-json/wp/v2/posts?_embed&per_page={args.maxPosts}&page={args.page}") {
result @type(name: "Posts") {
id
title
excerpt
date_gmt
slug
_embedded
}
headers @type(name: "Header") {
x_wp_totalpages
}
}
}
`;
2) Modify your ApolloLink
to use a custom responseTransformer
:
export const blogApiLink = new RestLink({
uri: process.env.BLOG_API_URI,
responseTransformer: async response => {
const result = await response.json();
return {
result: result,
headers: {
x_wp_totalpages: response.headers.get('x-wp-totalpages') // add whichever headers you want
}
};
}
});
...and that's it!
@efoken I have a very similar use case to yours, maybe this solution can work for you as well :smiley:
@lucasmafra Hi! I use this technic for my project and it works well for the default fetch-policy
(cache-first). I haven't checked exact your case. In my case I need to transform tree data to plain list.
Hi @cy6eria! Good to know it's working for you! I'm curious, have you ensured that the headers are being retrieved correctly for different requests? I'm saying that because:
1) AFAIK the @client
directive tells Apollo to get data from cache instead of from the server
2) if someone need to integrate with a REST API (which I think is one of the most common use cases for this thread), the headers must be nested (I mean, inside the query with @rest directive), instead of flat. To illustrate better, let's consider this code:
const getPosts = gql`
query posts {
posts @rest(type: "PostsPayload", path: "/wp-json/wp/v2/posts") {
id
title
}
headers @type(name: "Header") {
x_wp_totalpages
}
}
`;
The snippet above will have an undesired behavior with cache enabled, because the headers are not inside the query with @rest
directive, so Apollo does not associate the headers with that particular request. As a result, when you make the first request to the server it works fine, but in the second request Apollo will override the headers
in cache with the new value returned from the server, which can be bad depending on your use case.
So, to avoid that, you might need to put the headers
in a nested query like this:
const getPosts = gql`
query posts {
posts @rest(type: "PostsPayload", path: "/wp-json/wp/v2/posts") {
result {
id
title
}
headers @type(name: "Header") {
x_wp_totalpages
}
}
}
`;
Thanks for reporting this. There hasn't been any activity here in quite some time, so we'll close this issue for now. If this is still a problem (using a modern version of Apollo Client), please let us know. Thanks!
I'm still seeing this with the latest version of apollo client (3.1.11
at time of writing). extensions
written in an afterware are not available on data
unless I set a fetchPolicy
of no-cache
.
I'm finding it difficult to reproduce this in a sandbox because access to extensions
seems like it's been abstracted away in the plugin lifecycle; my own use case is working with an internal API served by graphql-java
so sharing that is difficult as well. If anyone has pointers on how to spin up a server or (or mock a server response) with extensions
let me know!
/reopen
@cy6eria Your solution is brilliant!
Does anybody know how to add a common variable to all the requests of apollo client. Suppose u have a lot of queries that take same variable with same value. Instead of defining and passing them on each query can we set a request interceptor to handle this ?
I am trying to modify response data following this guide https://github.com/apollographql/apollo-client/blob/master/Upgrade.md#afterware-data-manipulation
Apollo client config
saga
console:
problem: status key is present in afterware but absent in saga
minimal example to reproduce https://github.com/beornborn/apollo-client-2-response-issue
Version
How can I pass custom data in response?