Please, bear with me as this answer is long.
I ran into this issue as well. It seems the problem happens when using fragments (in this case, inline) and interfaces. I managed to solve it by passing the correct introspection data to Apollo's Heuristic Fragment Matcher (See Step 3).
Here's a detailed step by step guide on how to solve it:
1 - Verify Console Warnings.
Verify that there are warnings in your console (Here's an example that happened to me). These are the fields colliding with the default heuristic fragment matcher:
Reading the Apollo docs, I found out the following:
By default, Apollo Client's cache will use a heuristic fragment
matcher, which assumes that a fragment matched if the result included
all the fields in its selection set, and didn't match when any field
was missing. This works in most cases, but it also means that Apollo
Client cannot check the server response for you, and it cannot tell
you when you're manually writing invalid data into the store using
update, updateQuery, writeQuery, etc. Also, the heuristic fragment
matcher will not work accurately when using fragments with unions or
interfaces. Apollo Client will let you know this with a console
warning (in development), if it attempts to use the default heuristic
fragment matcher with unions/interfaces. The
IntrospectionFragmentMatcher is the solution for working with
unions/interfaces, and is explained in more detail below.
More info for v2 here:
https://www.apollographql.com/docs/react/v2.6/data/fragments/#fragments-on-unions-and-interfaces
More info for v3 here:
https://www.apollographql.com/docs/react/data/fragments/#using-fragments-with-unions-and-interfaces
To fix this issue, we need to pass the IntrospectionResultData to the Apollo Client (See Step 3). But before that, we need to generate the file or data.
You have 3 options. Either do it manually or automatically (remote or local).
2 - Generate the introspection file
Pick one of the options below (all of them end up being the same). Read all of them before choosing one.
2.1 - Option A - Generating the file manually.
Use the following schema to adapt it to your own.
Note, the following is TypeScript code. Remove the type
if you're using plain JS.
Please see that in my case I had in my .gql file a union type of the following fashion:
# GraphQL code omitted.
union PlanningResult = Planning | PlanningTechnical
// For Apollo V 2.x
export interface IntrospectionResultData {
__schema: {
types: {
kind: string;
name: string;
possibleTypes: {
name: string;
}[];
}[];
};
}
const result: IntrospectionResultData = {
__schema: {
types: [
{
kind: 'UNION',
name: 'PlanningResult',
possibleTypes: [
{
name: 'Planning',
},
{
name: 'PlanningTechnical',
},
],
},
],
},
};
export default result;
// For Apollo V3:
export interface PossibleTypesResultData {
possibleTypes: {
[key: string]: string[]
}
}
const result: PossibleTypesResultData = {
"possibleTypes": {
"PlanningResult": [
"Planning",
"PlanningTechnical"
]
}
};
export default result;
Once you've done this, proceed to step 3.
2.2 - Option B - Automatic Remote Approach.
This is if you have your schema in a remote server and you'd like to fetch it. This is a script extracted directly from the Apollo Docs. For the automatic approach, you can fetch the schema directly as stated in the Apollo Docs:
// This is for V2 only, for V3 use the link down below (They're not the same!).
// For V2: https://www.apollographql.com/docs/react/v2.6/data/fragments/#fragments-on-unions-and-interfaces
// For V3 please, go to https://www.apollographql.com/docs/react/data/fragments/#generating-possibletypes-automatically
const fetch = require('node-fetch');
const fs = require('fs');
fetch(`${YOUR_API_HOST}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
variables: {},
query: `
{
__schema {
types {
kind
name
possibleTypes {
name
}
}
}
}
`,
}),
})
.then(result => result.json())
.then(result => {
// here we're filtering out any type information unrelated to unions or interfaces
const filteredData = result.data.__schema.types.filter(
type => type.possibleTypes !== null,
);
result.data.__schema.types = filteredData;
fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => {
if (err) {
console.error('Error writing fragmentTypes file', err);
} else {
console.log('Fragment types successfully extracted!');
}
});
});
This will generate a json file with the __schema
and appropriate types. Once you've done this, proceed to step 3.
2.3 - Option C - Automatic Local Approach
The options above were difficult for me as I had my schema behind an auth wall. Fortunately, I did have direct local access to the .gql file and was able to generate the introspection file. Read on:
We use graphql-code-generator
to generate the introspection file for us.
Go to your back-end code, or wherever your graphql.gql file lies, and do:
- Install GraphQL Code Generator:
yarn add graphql
yarn add -D @graphql-codegen/cli
- Run the initialization wizard:
yarn graphql-codegen init
- Fill in the details (adapt to your own)
In my case, I selected:
- Backend - API or server, Application built with React
- Where is your schema? ./appsync/appSync.gql
- Pick plugins: Fragment Matcher (Feel free to select other plugins as well... this is the important one!)
- document: ./appsync/generated/introspection.ts (This is where you want the file to be outputted)
This will generate a codegen.yml
that will include the plugins and the configuration for graphql-code-generator
to run.
This is mine:
overwrite: true
schema: "./appsync/appSync.gql"
# documents: "./appsync/**/*.gql"
generates:
./appsync/generated/introspection.ts:
plugins:
# - "typescript"
# - "typescript-operations"
# - "typescript-resolvers"
# - "typescript-react-apollo"
- "fragment-matcher"
config:
# NOTE: Remember to specify the CORRECT Apollo Client Version
apolloClientVersion: 2.6
./graphql.schema.json:
plugins:
- "introspection"
I've commented on the parts that are not critical for our mission.
Then (very important!) Run:
yarn install
Because the wizard adds packages to our package.json
.
Then, generate the code:
yarn generate
This will output the introspection.ts file which needs to be included in Apollo to continue.
3 - Inject the introspection file to the ApolloClient
Now, in your front-end code, copy the introspection.ts
file to your repo (if it's not already in there), and include it:
Note: I've renamed my file to fragmentTypes.ts and included it inside the apollo folder:
For V2:
import ApolloClient from 'apollo-client/ApolloClient';
import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import { InMemoryCache } from 'apollo-cache-inmemory/lib/inMemoryCache';
// The file we just generated. If it's a .json file
// remember to include the .json extension
import introspectionQueryResultData from './apollo/fragmentTypes';
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData,
});
export const globalClient = new ApolloClient({
link,
cache: new InMemoryCache({ fragmentMatcher }),
});
For V3:
import { InMemoryCache, ApolloClient } from '@apollo/client';
// In case you used graphql-code-generator
// import introspectionQueryResultData from './apollo/fragmentTypes';
// The file we just generated. If it's a .json file
// remember to include the .json extension
import possibleTypes from './path/to/possibleTypes.json';
const cache = new InMemoryCache({
possibleTypes,
});
const client = new ApolloClient({
// ...other arguments...
cache,
});
After this, your console warnings should go away and the queries and mutations should perform as normal.