Troubleshooting Common Issues in Apollo GraphQL – Apollo Client
Project Description
Apollo Client is a JavaScript library that helps you consume GraphQL APIs. It is designed to be flexible and easy to use, and it works with a variety of JavaScript platforms, including React, Angular, Vue, and more.
With Apollo Client, you can use GraphQL to query your APIs and get exactly the data you need, in a predictable and strongly-typed way. Apollo Client also makes it easy to manage your application’s local state, and to perform mutations (such as creating, updating, or deleting data) on the server.
To use Apollo Client, you’ll need to install it in your project using npm:
npm install apollo-client
Then, you can import the library into your JavaScript code and use it to create an Apollo Client instance:
import { ApolloClient, HttpLink, InMemoryCache } from 'apollo-client';
const client = new ApolloClient({
link: new HttpLink({ uri: '/graphql' }),
cache: new InMemoryCache()
});
With the client instance created, you can use it to make GraphQL queries and mutations in your app. For example, you can use the query
method to execute a GraphQL query and get the results, or the mutate
method to perform a mutation on the server.
Troubleshooting Apollo GraphQL – Apollo Client with the Lightrun Developer Observability Platform
Lightrun is a Developer Observability Platform, allowing developers to add telemetry to live applications in real-time, on-demand, and right from the IDE.
- Instantly add logs to, set metrics in, and take snapshots of live applications
- Insights delivered straight to your IDE or CLI
- Works where you do: dev, QA, staging, CI/CD, and production
The most common issues for Apollo GraphQL – Apollo Client are:
SSR – Send request with cookie from apollo client with Next.js
Here is a modification of the official nextjs example, which passes the cookie from nextjs ctx to fetch on ssr.
import React from "react"
import Head from "next/head"
import { ApolloProvider } from "@apollo/react-hooks"
import { ApolloClient } from "apollo-client"
import { InMemoryCache } from "apollo-cache-inmemory"
import { HttpLink } from "apollo-link-http"
import fetch from "isomorphic-unfetch"
let apolloClient = null
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState)
return (
<ApolloProvider client={client} >
<PageComponent {...pageProps} />
</ApolloProvider>
)
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName =
PageComponent.displayName || PageComponent.name || "Component"
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.")
}
WithApollo.displayName = `withApollo(${displayName})`
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient({}, ctx.req.headers.cookie))
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("@apollo/react-ssr")
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient
}}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
}
// Extract query data from the Apollo store
// @ts-ignore
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState
}
}
}
return WithApollo
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState = {}, cookie = "") {
// Make sure to create a new client for every server-side request so that data
// isn"t shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, cookie)
}
// Reuse client on the client-side
if (!apolloClient) {
// @ts-ignore
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}, cookie) {
const enchancedFetch = (url, init) => {
return fetch(url, {
...init,
headers: {
...init.headers,
"Cookie": cookie
}
}).then(response => response)
}
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
ssrMode: typeof window === "undefined", // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: "http://localhost:3000/api", // Server URL (must be absolute)
credentials: "same-origin", // Additional fetch() options like `credentials` or `headers`
fetch: enchancedFetch
}),
cache: new InMemoryCache().restore(initialState)
})
}
useMutation updates cache but does not re-render components querying same object
Many of us share a similar conundrum: when the cache is altered, useQuery hook triggers a rerender – except in cases where the modified object within the cache wasn’t referenced originally during render. To illustrate this point more clearly, here is an example from a particular situation.
type Me {
matchmakings: [Matchmaking!]!
...
}
type Matchmaking {
joined: DateTime
...
}
With this component, a single query is all that’s needed to fetch personal information and matchmaking.
Case number 1: the component is rendered only after “me” has already been queried and is in the cache: {me && <MyComponent/>}
=> In that case, when I do cache.modify
to change one of the matchmaking.joined date, the component rerenders (no bug).
Case number 2: the component is rendered before “me” is in the cache. => first time the component renders, “me” is null (loading from backend) => later on, when I do cache.modify
to change one of the matchmaking.joined date referred to by the “Me” object that was fetched meanwhile, the component is not rerendered
In order to remedy this bug, it is critical that the “useQuery” hook refreshes all cache references in an efficient manner. This will ensure necessary updates are triggered when changes occur within the pre-existing cached objects.
Unsure how to resolve `Missing field while writing result` error when using subscriptions
The “Missing field while writing result” error can occur when using Apollo Client’s GraphQL subscriptions if the server is returning a response that does not include a field that is expected by the client. This can happen if the server schema has changed, or if there is a discrepancy between the server and client schemas.
To resolve this error, you’ll need to ensure that the server is returning a response that includes all the fields that are expected by the client. This may involve updating the server schema to match the client schema, or updating the client schema to match the server schema.
If you’re using Apollo Server to implement your GraphQL server, you can use the apollo-link-schema
library to validate the server and client schemas against each other. This library provides a validate
function that you can use to compare the two schemas and identify any discrepancies.
Here’s an example of how you might use apollo-link-schema
to validate the server and client schemas:
import { makeExecutableSchema, mergeSchemas } from 'apollo-server';
import { validate } from 'apollo-link-schema';
const serverSchema = makeExecutableSchema({
typeDefs: `
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'Hello, world!'
}
}
});
const clientSchema = `
type Query {
hello: String
}
`;
const errors = validate(serverSchema, clientSchema);
if (errors.length > 0) {
console.log(`Errors found in client schema:`);
errors.forEach((error) => {
console.log(error.message);
});
} else {
console.log(`Server and client schemas are compatible!`);
}
useQuery continuously fetches; loading always true
After a thorough investigation, the issue was determined to be related to the timestamp included in one of the variables.
const time = useRef(DateTime.local().toString())
Typescript build error “Generic type ‘ExecutionResult<TData>’ requires between 0 and 1 type arguments” with graphql@14
Upgrading your “graphql” dependency to version 15.5.0 may provide a solution.
refetchQueries not working when using string array after mutation
The process runs efficiently when existing items are removed, but not so much while introducing them.
Although it may not be obvious, this behavior is an effect of the component that triggers a query on deletion remaining intact while redirecting to another page during item creation. This act fundamentally changes how the query behaves and affects what can be seen afterward.
Apollo maintains a list of observable queries at any given time, which is based on which components are currently mounted that defined a query (i.e. through <Query>...</Query>
or useQuery
). When a component gets unmounted – at least in the React world -, Apollo removes it from the list of observable queries by calling QueryManager.tearDownQuery
when the component unmounts. During refetchQueries
, when given a Query name string, apollo searches only the observable queries and because it can’t find it, it doesn’t execute a refeetch. If you provide an { query: ..., variables: .. }
as a refetch value, Apollo bypasses this search and always executes the query you’ve given it, as if it was a completely new query (since it has the doc and the variables it needs to execute it).
refetchQueries
will only work with strings if the component that defined the original query is not unmounted. On the contrary, it will always work when using the { query... , variables: ... }
style.\
useQuery not returning data when mock doesn’t match query shape
To troubleshoot this issue, there are a few things you can check:
- Make sure that the mock data you’re using matches the shape of the data returned by the actual GraphQL query. If the shape of the mock data doesn’t match the actual data, the
useQuery
hook will not be able to properly parse the data and it will not be returned. - Check the
error
field of the object returned byuseQuery
. If an error is present, it could be a sign that there is a problem with your GraphQL query or the mock data you’re using. - If you’re using the
useLazyQuery
hook instead ofuseQuery
, make sure that you’re calling the returned fetch function in order to execute the query.
onError and onCompleted callbacks do not work in useQuery hook
If you’re using the useQuery
hook from Apollo GraphQL and you’re experiencing an issue where the onError
and onCompleted
callbacks are not being called, there are a few things you can try:
- Make sure that the
useQuery
hook is being called inside a component that is being rendered to the DOM. TheuseQuery
hook will not execute if it is called in a component that is not being rendered. - Check the
error
field of the object returned byuseQuery
. If an error is present, theonError
callback should be called. - If the query is returning data, the
onCompleted
callback should be called. Make sure that the query is actually returning data and that the data is being used in the component. - Make sure that the
onError
andonCompleted
callbacks are being passed as props to theuseQuery
hook.
Using useQuery with pollInterval triggers onCompleted only once
A resolution to this issue can be achieved by settingnotifyOnNetworkStatusChange
to true
.
Invalid hook call when used inside React Native
If you’re using the Apollo GraphQL client in a React Native app and you’re seeing an error that says “Invalid hook call,” it means that you’re trying to use a hook inside a function that is not a React component.
Hooks can only be used inside of React functional components or inside the body of a custom hook. They cannot be used inside of a regular JavaScript function.
To fix this error, you’ll need to refactor your code to make sure that the hook is being called inside a React component or inside a custom hook.
More issues from Apollo GraphQLrepos
Troubleshooting apollo-graphql-apollo-tooling | Troubleshooting apollo-graphql-graphql-subscriptions
It’s Really not that Complicated.
You can actually understand what’s going on inside your live applications.