apollo-client-nextjs icon indicating copy to clipboard operation
apollo-client-nextjs copied to clipboard

Using fragments in combination with `PreloadQuery` leads to `unique-names` warnings

Open dlehmhus opened this issue 1 year ago • 16 comments

What's the matter?

When using the PreloadQuery component with a query that contains fragments, we get following unique-names warning in the console:

Warning: fragment with name Compoany already exists.
graphql-tag enforces all fragment names across your application to be unique; read more about
this in the docs: http://dev.apollodata.com/core/fragments.html#unique-names

What do we expect?

Using PreloadQuery should not lead to unique-names warnings.

Also: The links is not very helpful because the page doesn't exist anymore.

How to reproduce?

Take a look into the console of following example https://stackblitz.com/edit/nextjs-869gd8?file=app%2Fexample-query.ts,app%2Fpage.tsx

dlehmhus avatar Jul 03 '24 13:07 dlehmhus

This is a weird one, and it seems to be a quirk of how React implements Client Component imports.

I'll try to break it down, but this graphic might also help:

image

So, what happens here is that our RSC component imports from queryFile.js. That executes gql'interface MyInterface ... for the first time. It also imports from clientFile.jsx. Now, nothing imported from a Client File is actually imported into the RSC context. So it doesn't really import the function MyUserComponent, but a "client reference" - an object that describes that this is a reference into a client file, to be actually imported and used during a Client Component render. At the same time, though, it needs to know if that export exists and what that export actually is (is it a Component or something else you couldn't even reference in RSC?). For that, clientFile.jsx needs to be executed. And clientFile.jsx also imports from queryFile.js. Mind that now we are in a "client import context" though, so this queryFile.js is now treated as a different file than the original queryFile.js - it needs to be executed again to find out what's exported from there.

Unfortunately, that executes the gql call again, and that triggers the graphql-tag warning that you are parsing a fragment of a name that's already known.

I'll have to dig deeper to find a solution for this, and I guess I'll have to make some changes to graphql-tag to that. (I'll use that as an opportunity to update those urls as well, and give the package some maintenance.)

I can't give you a definite timeline on that - we're about to release Apollo Client 3.11 RC next week, so that has priority for now. I hope I can get to it once 3.11 has been released.

In the meantime, you can disable that warning with

import { disableFragmentWarnings } from 'graphql-tag';

disableFragmentWarnings()

PS: Gruesse nach Hamburg :)

phryneas avatar Jul 04 '24 08:07 phryneas

@phryneas thanks a lot for the detailed explanation und liebe Grüße zurück.

dlehmhus avatar Jul 04 '24 13:07 dlehmhus

@phryneas Thanks for the details. Do you think this could also lead to refetchQueries not working properly? I suspect this is the culprit for my issues.

Algram avatar Jul 22 '24 08:07 Algram

@Algram I would not expect so, but if you can create a reproduction, please open a new issue and I'll take a look.

phryneas avatar Jul 22 '24 08:07 phryneas

I think this is an issue with useSuspenseQuery as well. In this case, the query is executed on the server side, and the query and data are written into the HTML response in manual-transport.ssr.js:

// src/ManualDataTransport/dataTransport.ts
function transportDataToJS(data, stringify2) {
  const key = Symbol.keyFor(ApolloSSRDataTransport);
  return `(window[Symbol.for("${key}")] ??= []).push(${htmlEscapeJsonString(
    stringify2(data)
  )})`;
}

On the client side, the query is parse-stringify-parsed in @apollo/client-react-streaming:

function deserializeOptions(options) {
  return {
    ...options,
    // `gql` memoizes results, but based on the input string.
    // We parse-stringify-parse here to ensure that our minified query
    // has the best chance of being the referential same query as the one used in
    // client-side code.
    query: gql(print(gql(options.query)))
  };
}

The query that is written to the HTML response has newlines removed, but the query after being parse-stringified has the newlines present. This causes gql to consider it a different query, which results in the duplicate fragment warning.

Perhaps remove newlines when memoizing the results of parsing the query?

Edit: a bit more digging reveals that the newlines are actually removed in @apollo/client-react-streaming, index.ssr.js:

// src/DataTransportAbstraction/transportedOptions.ts
function serializeOptions(options) {
  return {
    ...options,
    query: printMinified(options.query)
  };
}

joaquimds avatar Jul 27 '24 16:07 joaquimds

Thanks for bringing that up, that's another good point.

Even if it were 100% the same, you'd get that warning... The graphql-tag package was just made years before these patterns even existed. Like I said, I'll have to do some work there, but I can't give you a timeline - we have quite a lot of things to take care of. It's definitely on the list!

For now, please disable the warnings.

phryneas avatar Jul 29 '24 06:07 phryneas

@phryneas We are using gql.tada and not graphql-tag. How would we go about disabling the warnings in that case?

Algram avatar Jul 31 '24 07:07 Algram

@Algram I'll have to look into that. Could you please give me the full text of the warning?

phryneas avatar Jul 31 '24 07:07 phryneas

@phryneas Sure:

[warn] Warning: fragment with name PageInfo already exists.
graphql-tag enforces all fragment names across your application to be unique; read more about
this in the docs: http://dev.apollodata.com/core/fragments.html#unique-names

Algram avatar Jul 31 '24 07:07 Algram

@Algram That error message seems to come from graphql-tag, not from gql.tada. Have you tried the method I've outlined above?

phryneas avatar Jul 31 '24 07:07 phryneas

I have tried it and it doesn't work. graphql-tag is coming as a dependency of apollo and produces the error. We are not using graphql-tag for defining our fragments though, but rather gql.tada. I guess the disableFragmentWarnings function does not work in such a setup (?)

Algram avatar Jul 31 '24 07:07 Algram

Can you try

import { disableFragmentWarnings } from '@apollo/client';

disableFragmentWarnings()

instead?

phryneas avatar Jul 31 '24 08:07 phryneas

@phryneas we're getting the same fragment unique-names warning, we tried to disable it by importing (and calling) disableFragmentWarnings from either graphql-tag or @apollo/client, without effect so far.

nduchon avatar Sep 25 '24 17:09 nduchon

@nduchon did you make sure to do that call in the client bundle, meaning in a file with "use client"?

phryneas avatar Oct 07 '24 07:10 phryneas

Hi,

We have the same problem here using useBackgroundQuery and useReadyQuery. fragments are defined in only one place yet we have duplicate fragment warnings on page load (Next.js).

Suppressing the warnings clears these from the console, though. Thanks for that suggestion!

Anything I can provide that might help with finding a fix?

rob-strover-axomic avatar Dec 13 '24 16:12 rob-strover-axomic

disableFragmentWarnings()

That works, thank you. Any permanent solution ahead?

julianCast avatar May 13 '25 12:05 julianCast