apollo-client: Can't modify response with afterware

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

//@flow
import ApolloClient from 'apollo-client'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { ApolloLink } from 'apollo-link'

const httpLink = new HttpLink({ uri: "https://swapi.apis.guru" })
const addStatusLink = new ApolloLink((operation, forward) => {
  return forward(operation).map((response) => {
    response.data.status = 'SUCCESS'
    console.log('afterware', JSON.stringify(response))
    return response;
  })
})

const link = addStatusLink.concat(httpLink)

const cache = new InMemoryCache()

export const apolloClient = new ApolloClient({
  link,
  cache: cache.restore(window.__APOLLO_STATE__),
})

saga

//@flow
import { takeEvery } from 'redux-saga'
import { SAGA_GET_STAR_WARS } from '~/app/reducers/Saga'
import { getLuke } from '~/app/api/StarWars'
import { apolloClient } from '~/app/Apollo'

export function* perform(_a: Object): Generator<*, *, *> {
  const response = yield apolloClient.query({
    query: getLuke,
    variables: {id: 'cGVvcGxlOjE='}
  })
  console.log('saga', JSON.stringify(response))
}

export function* watch(): Generator<*, *, *> {
  yield* takeEvery(SAGA_GET_STAR_WARS, perform)
}

export default watch

console:

afterware {"data":{"person":{"name":"Luke Skywalker","__typename":"Person"},"status":"SUCCESS"}}

saga {"data":{"person":{"name":"Luke Skywalker","__typename":"Person"}},"loading":false,"networkStatus":7,"stale":false}

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

  • apollo-client@2.0.0-rc.7

How can I pass custom data in response?

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Comments: 26 (3 by maintainers)

Most upvoted comments

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 🤓

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.

@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.

@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

@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

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

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 ?

@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