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
448 stars 35 forks source link

Error: could not identify unique query #386

Open dameradev opened 5 days ago

dameradev commented 5 days ago

Hello, we recently updated to "@apollo/experimental-nextjs-app-support"; We need this to run server queries.

The problem is that now our global store queries are throwing the error below when on first page visit/refresh.


Versions

"@apollo/client": "^3.11.8",
"@apollo/experimental-nextjs-app-support": "^0.11.5",




This is the error we're getting

 const { serverQuery } = queryManager.getDocumentInfo(transformedDocument);
  1540 |     if (!serverQuery) {
> 1541 |       throw new Error("could not identify unique query");
       | ^
  1542 |     }
  1543 |     const canonicalVariables = canonicalStringify(options.variables || {});
  1544 |     const cacheKeyArr = [print(serverQuery), canonicalVariables];




Below is how we've setup the apollo wrapper


"use client";
import { HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  ApolloClient,
} from "@apollo/experimental-nextjs-app-support";
import { getApiEndPoint } from "src/config";
import cache from '../src/lib/apollo/storage/cache';

// have a function to create a client for you
function makeClient(token) {
  const httpLink = new HttpLink({

    // this needs to be an absolute url, as relative urls cannot be used in SSR
    uri: getApiEndPoint(),
    headers: {
      authorization: `Bearer ${typeof window !== "undefined" ? localStorage.getItem("token") : token}`,
    },
    // you can override the default `fetchOptions` on a per query basis
    // via the `context` property on the options passed as a second argument
    // to an Apollo Client data fetching hook, e.g.:
  });

  // use the `ApolloClient` from "@apollo/experimental-nextjs-app-support"
  return new ApolloClient({
    // use the `InMemoryCache` from "@apollo/experimental-nextjs-app-support"
    // fetchOptions: { cache: "no-store" },
    cache,
    link: httpLink,
  });
}

// you need to create a component to wrap your app in
export function ApolloWrapper({ children, cookieToken }: { children: React.ReactNode, cookieToken: string }) {

  const client = makeClient(cookieToken); // Create Apollo client instance
  return (
    <ApolloNextAppProvider makeClient={() => client}>
      {children}
    </ApolloNextAppProvider>
  );
}




Here's the query that's erroring out (one of them, all global store queries are erroring out)

import { useQuery } from "@apollo/client";
import { GET_USER_STORE } from "../../queries";
import { storeVar } from "./apolloVars";

export default function useStore() {
  const { data } = useQuery(GET_USER_STORE, {
    fetchPolicy: "cache-only",
  });

  const store = data?.store;

  return {
    name: store?.name,
    address: store?.address,
    line_1: store?.line_1,
    city: store?.city,
    zipCode: store?.zipCode,
    country: store?.country,
    warehouseId: store?.warehouseId,
    external_ref: store?.external_ref,
    basePrice: store?.basePrice,
    id: store?.id,
    termsAndConditionsAccepted: store?.termsAndConditionsAccepted,
    users: store?.users,
    priority: store?.priority,
    customerDefinition: store?.customerDefinition,
    warehouseName: store?.warehouseName,
    currency: store?.currency,
    salesperson: store?.salesperson,
    status: store?.status,

    addStore({
      name,
      address,
      warehouseId,
      external_ref,
      basePrice,
      line_1,
      city,
      zipCode,
      country,
      id,
      termsAndConditionsAccepted,
      users,
      priority,
      customerDefinition,
      warehouseName,
      currency,
      salesperson,
      status
    }) {

      storeVar({
        name : name || store?.name, 
        address: address || store?.address,
        warehouseId: warehouseId || store?.warehouseId,
        country: country || store?.country,
        external_ref: external_ref || store?.external_ref,
        basePrice: basePrice || store?.basePrice,
        line_1: line_1 || store?.line_1,
        city: city || store?.city,
        zipCode : zipCode || store?.zipCode,
        id : id || store?.id,
        termsAndConditionsAccepted : termsAndConditionsAccepted || store?.termsAndConditionsAccepted,
        users : users || store?.users,
        priority : priority || store?.priority,
        customerDefinition : customerDefinition || store?.customerDefinition,
        warehouseName : warehouseName || store?.warehouseName,
        currency : currency || store?.currency,
        salesperson : salesperson || store?.salesperson,
        status: status || store?.status,
      });
    },
  };
}




Here's how it's all defined


export const GET_USER_STORE = gql`
  query GET_USER_STORE {
    store @client 
  }
`;
export const storeVar = makeVar<Store>(InitalStore);
phryneas commented 5 days ago

I just commented in the forum, but let's continue the conversation here:

We cannot stream queries from the server to the client that contain only client-local fields.

Also keep in mind that makeVar has the risk here to share data between all your users during SSR. It's like a global variable.

dameradev commented 5 days ago

I just commented in the forum, but let's continue the conversation here:

We cannot stream queries from the server to the client that contain only client-local fields.

Also keep in mind that makeVar has the risk here to share data between all your users during SSR. It's like a global variable.

So what's the solution here?

How can I make sure to stream these queries only on the client?

phryneas commented 5 days ago

I'm afraid, you probably won't be able to use makeVar, just like any other global state solution, with a SSR approach like Next.js at all.

Even if we could solve the "data of multiple users is getting mixed up" problem, every future render on the server would not know what data the user has set in the browser, so it would render "initial data" again.


If you can ensure that all of this will only be accessed in the browser (keep in mind that Next.js SSRs your Client Component, too!), I would recommend skipping Apollo Client here and calling useReactiveVar instead of useQuery/useSuspenseQuery.

That said, this is a very strong if. You probably would have to avoid rendering whole subtrees of your app on the server, with something like

const isOnServer = useSyncExternalStore(() => {}, () => false, () => true)

and then conditionally rendering children or not.