• 01-Jan-2023
Lightrun Team
Author Lightrun Team
Share
This is a glossary of all the common issues in Apollo GraphQL - Apollo Client

Troubleshooting Common Issues in Apollo GraphQL – Apollo Client

Lightrun Team
Lightrun Team
01-Jan-2023

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

 

Getting a sense of what’s actually happening inside a live application is a frustrating experience, one that relies mostly on querying and observing whatever logs were written during development.
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:

  1. 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.
  2. Check the error field of the object returned by useQuery. 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.
  3. If you’re using the useLazyQuery hook instead of useQuery, 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:

  1. Make sure that the useQuery hook is being called inside a component that is being rendered to the DOM. The useQuery hook will not execute if it is called in a component that is not being rendered.
  2. Check the error field of the object returned by useQuery. If an error is present, the onError callback should be called.
  3. 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.
  4. Make sure that the onError and onCompleted callbacks are being passed as props to the useQuery 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

 

Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications. It’s a registration form away.

Get Lightrun

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By submitting this form, I agree to Lightrun’s Privacy Policy and Terms of Use.