It's 2019, and I am just getting started with GraphQL. Coming from a REST background, one intuitive pattern that I found myself implementing was:
- Fetch data with Query
- Perform updates on the data
- Pass that data as input for a Mutation
However, when I tried that, I got stuck with an error:
"problems": [{
"path": [
"__typename"
],
"explanation": "Field is not defined on ProductInput"
}
]
Where did __typename
come from? It wasn't in the Query response.
It turns out the Apollo Client library adds __typename
field for its InMemoryCache. Apollo Client's issue #2160 is tracking the same issue, and the thread suggests removing this field either on a case-by-case basis or have it automatically removed with Apollo Link. I will show you how to do the latter.
Solution
This solution is based off some recommendations from Issue #2160.
Imports
import ApolloClient from "apollo-client";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import { ApolloLink } from "apollo-link";
import { ApolloProvider } from "react-apollo";
Implement OmitDeep
const omitDeepArrayWalk = (arr, key) => {
return arr.map(val => {
if (Array.isArray(val)) return omitDeepArrayWalk(val, key);
else if (typeof val === "object") return omitDeep(val, key);
return val;
});
};
const omitDeep = (obj, key) => {
const keys = Object.keys(obj);
const newObj = {};
keys.forEach(i => {
if (i !== key) {
const val = obj[i];
if (val instanceof Date) newObj[i] = val;
else if (Array.isArray(val)) newObj[i] = omitDeepArrayWalk(val, key);
else if (typeof val === "object" && val !== null)
newObj[i] = omitDeep(val, key);
else newObj[i] = val;
}
});
return newObj;
};
Implement Apollo Link to remove __typenames
const cleanTypenameLink = new ApolloLink((operation, forward) => {
if (operation.variables && !operation.variables.file) {
// eslint-disable-next-line
operation.variables = omitDeep(operation.variables, "__typename");
}
return forward(operation);
});
Create an Apollo Client
const link = createHttpLink({
uri: "/graphql"
});
const client = new ApolloClient({
link: ApolloLink.from([
cleanTypenameLink, link
]),
cache: new InMemoryCache()
});
In case if you're using Apollo Boost, and want Boost's behavior, initialize Client as:
import { onError } from "apollo-link-error";
const link = createHttpLink({
uri: "/graphql",
});
const client = new ApolloClient({
link: ApolloLink.from([
cleanTypenameLink,
onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.map(({ message, locations, path }) =>
console.log(
`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
}),
link
]),
cache: new InMemoryCache()
});
More about achieving Boost-like behavior using Apollo Client is documented here.