apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
358 stars 25 forks source link

Error: could not identify unique query (after upgrading to 0.4.1) #74

Closed robhowell closed 9 months ago

robhowell commented 9 months ago

After upgrading @apollo/client from 3.8.0-beta.5 to 3.8.0-rc.2 and upgrading @apollo/experimental-nextjs-app-support from 0.3.1 to 0.4.1, I'm seeing the following error: Error: could not identify unique query.

In-browser stack trace shown below:

Screenshot 2023-08-02 at 8 01 26 pm

The dev console shows this:

NextSSRApolloClient.js:32 Uncaught Error: could not identify unique query
    at NextSSRApolloClient.identifyUniqueQuery (webpack-internal:///(app-pages-browser)/./node_modules/@apollo/experimental-nextjs-app-support/dist/ssr/NextSSRApolloClient.js:32:19)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/@apollo/experimental-nextjs-app-support/dist/ssr/NextSSRApolloClient.js:45:63)
    at Object.push (webpack-internal:///(app-pages-browser)/./node_modules/@apollo/experimental-nextjs-app-support/dist/ssr/lateInitializingQueue.js:17:25)
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/@apollo/experimental-nextjs-app-support/dist/ssr/dataTransport.js:29:186)
    at Object.push (webpack-internal:///(app-pages-browser)/./node_modules/@apollo/experimental-nextjs-app-support/dist/ssr/lateInitializingQueue.js:17:25)
    at living-room-furniture:20:2078

We're using Next version 13.4.12, using the app directory.

We're in the process of upgrading a large existing app to React 18 with Next.js 13, so there are still a number of other issues that we need to fix. There are still some outstanding hydration errors that need to be resolved - including hydration errors which result in the page switching to client-side-only rendering - so it is possible that this error is only occurring because of the other outstanding issues. But this error did not show before I upgraded, so it seems to be related to recent changes in the @apollo/experimental-nextjs-app-support package.

Has anybody else hit this issue, or has any suggestions about how I could resolve it?

jerelmiller commented 9 months ago

Hey @robhowell 👋

Would you be able to provide a bit more info on how this is being triggered? Which hooks are you using, etc. Alternatively, a reproduction of the issue would be super helpful.

Thanks for bringing this to our attention!

phryneas commented 9 months ago

Can you share which query is triggering this? Is this maybe a client-only query?

robhowell commented 9 months ago

@jerelmiller @phryneas Sorry, I forgot to specify which exact hook was being used. After further investigation I have a number of extra details that limit the scope significantly and may help isolate the root cause:

Other possibly useful info:

This is in an existing project with quite a lot of tech debt that we're working through as part of the migration to Next.js 13 & React 18. So it is very possible that one (or a combination) of these other factors could be triggering this error:

Sorry, but I'm not able to share the source code for this project, but here is a short snippet from where we are making the actual call:

const { data: initialData, loading } = useSuspenseQuery(query, {
    fetchPolicy: 'cache-and-network',
    variables: {
      ...variables,
      limit: NUMBER_OF_PRODUCTS_TO_SHOW,
    },
    ...otherOptions,
  });

It is possible that this issue will resolve itself once some of the other issues in this app are fixed (e.g. hydration errors). I'll continue working through those issues, and will report back if any of those changes help.

phryneas commented 9 months ago

@robhowell I just realized that "client-only" in the context of Next.js was very unspecific 😅 Did the query actually contain fields to be requested from the server, or did it only request fields marked with the @client directive?

Can you show the query in question? This is caused by that query being "special" in some way.

robhowell commented 9 months ago

@jerelmiller @phryneas Thanks a lot for your quick responses btw, and thanks so much for creating this package! The project that I'm working on has been using Apollo Client for a few years so it is great to be able to still use it with React 18 & Next.js, without a lot of custom configuration. We still have a large number of components to migrate, but using Suspense & streaming SSR with Apollo should hopefully speed up many parts of the app :)

@phryneas Ah, that kind of "client-only"! No, we have used local resolvers etc in the past (still used in some other areas of the app) but not in this query.

I can't see anything especially unusual in this query. It will be okay to share this query here since this can be publicly seen in the browser dev tools for this app anyway:

export const GET_PRODUCTS_PAGE = gql`
  query getProductsPage(
    $limit: Int
    $slug: String!
    $zipCode: String
    $stateCode: String
    $regionId: String
    $refreshCache: Boolean
  ) {
    getProductsPage(
      limit: $limit
      slug: $slug
      zipCode: $zipCode
      stateCode: $stateCode
      regionId: $regionId
      refreshCache: $refreshCache
    ) {
      filters {
        id
        label
        options {
          id
          label
        }
      }
      pageHtmlTitle
      pageId
      pageLongDescription
      pageLongName
      pageMetaDescription
      pageName
      products {
        campaign
        attributes {
          id
          values
        }
        id
        name
        onDisplayInShowroom
        path
        productFamily
        productLabel
        productType
        quickship
        rangeAttributes {
          id
          value
        }
        secondaryImage
        secondaryImageCropData
        slug
        sortOrder {
          featured
          newest
          popularity
        }
        variants {
          image
          label
          originalPrice
          path
          price
          quickship
          isOutOfStock
          sku
          slug
          tier
        }
        variantsTotal
      }
      totalProductCount
      upholsteryOptions {
        color
        id
        label
        thumbnail
      }
    }
  }
`;
phryneas commented 9 months ago

Huh, that is very weird.

As you can see here in the sources that error should only ever appear if the query that is being transported does not contain any "server part". Could you set a breakpoint at that error, and then execute print(options.query) in the console and share the output? Maybe this query has been mangled in some way before it was transported to the client 🤔

robhowell commented 9 months ago

Thanks @phryneas, that is really helpful!

I added a console log inside the if (!serverQuery) conditional so I was able to see more details about the query that is throwing the error. I'm not sure how/why using useSuspenseQuery on the same page could affect this, but the error is actually being triggered by a completely separate query that uses local fields, and makes that request via useQuery.

Most of the global state in this app is not tightly bound to the data we load from the API via Apollo Client, so we have actually started to migrate the small number of local fields & Reactive Variables currently used over to Jotai. I'll prioritise migrating that state asap, which should resolve this issue for us.

I would rather not share the exact details of this other query publicly, but if you would like to debug this issue further (in case other users hit this in future) then I'd be happy to email it to you if that would be helpful?

phryneas commented 9 months ago

I think we're onto something here, even without that email :)

Two things:

  1. I suspect in this case, your problem is caused because you use useQuery from @apollo/client, not from @apollo/experimental-nextjs-app-support (please don't mix these!). Can you verify that changing that over would prevent this from occuring?
  2. If you then change that from useQuery to useSuspenseQuery, will the problem start occuring again? (I suspect yes, and that would be something we have to fix).
robhowell commented 9 months ago

@phryneas That totally makes sense, there are a lot of older queries in the codebase that are still importing from @apollo/client.

I've just checked and that specific query is using an even older approach, one of the higher order components - imported like this:

import { graphql } from '@apollo/client/react/hoc';

It doesn't look like import { graphql } from '@apollo/experimental-nextjs-app-support/react/hoc'; works, so I assume that the react/hoc exports are not available from the new package.

It is used in an old class component, so I'll need to convert it to a function component first. I'll do that this evening, then I can see if the bug still occurs when using useQuery or useSuspenseQuery.

Sorry if I've wasted your time with this, from a quick search online I've just found this info on the docs page:

Note: Official support for React Apollo higher order components ended in March 2020. This library is still included in the @apollo/client package, but it no longer receives feature updates or bug fixes.

So it looks like this project should have stopped using those features over 3 years ago! Given that it was deprecated such a long time ago, I don't anybody would reasonably expect that functionality to be supported in this new library, and I assume that is why we're seeing this error.

We have a number of instances of graphql and withApollo still being used across the codebase, so as part of this migration we'll convert them to function components where necessary, then use useSuspenseQuery for those calls instead.

phryneas commented 9 months ago

I'll at least try to explain to you what's happening here, so you can maybe find a workaround. A few puzzle pieces:

So, in your case, you'll have to avoid that query the same way we do: https://github.com/apollographql/apollo-client-nextjs/blob/771a39fe824a344df6900d0593d465ba58631294/package/src/ssr/hooks.ts#L18-L21 We change the cache-policy so no actual query happens and is transported over.

There might still be a few more puzzle pieces, since I've never personally had to deal with the HOCs, but I hope that's already enough to help you through this :)

robhowell commented 9 months ago

@phryneas Thanks a lot, that is really helpful!

I'll report back after I've converted that component and updated it to use useSuspenseQuery and will report back to confirm if/when it resolves the issue.

robhowell commented 9 months ago

@phryneas After replacing the Higher Order Components used in that component I'm no longer seeing this error, so I'll close this issue now. Thanks for all of your help, we really appreciate it.