"@apollo/react-hooks": "^3.1.3",
"apollo-client": "^2.6.8",
Apollo client return undefined on react app but return the data on gql playground, I don't understand why don't it works on client-side but works on graphql playground.
Schema
I have defined union for user query for error handling.
type Query {
user(id: ID!): UserReturn!
}
union UserReturn = User | Error
type User {
id: ID!
username: String!
email: String
profileUrl: String
createdAt: Date
ads: [Doc!]!
}
type Error {
message: String
code: ID
}
Query resolver
async user(_, { id }, { User }) {
console.log('query - User')
try {
await delay(1000 * 3)
const user = await User.findById(id).populate('userData')
console.log(user)
if (!user) return {
__typename: 'Error',
message: 'User not found.',
code: id
}
const { _id: id, username, email, createdAt, userData: { profileUrl } } = user
console.log(username)
return {
__typename: 'User',
id,
username,
email,
createdAt,
profileUrl
}
} catch (err) {
console.log(err)
return {
__typename: 'Error',
message: 'Something went wrong while getting user.',
code: LogBack(err, `query/user?id=${id}`, __filename)
}
}
}
When querying on gql playground
on graphql playground, query works.
On the client-side
const { data } = useQuery(
gql`query user($id: ID!) {
user(id: $id) {
__typename
... on User {
id
username
email
profileUrl
createdAt
# ads
}
... on Error {
message
code
}
}
}
`,
{
variables: {
id: userId
}
}
);
console.log(data) // undefined
useQuery runs but returns undefiend.
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:
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).
Pick one of the options below (all of them end up being the same). Read all of them before choosing one.
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.
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.
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:
yarn add graphql
yarn add -D @graphql-codegen/cli
yarn graphql-codegen init
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.
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.