Hey there, I tried to update the @apollo/client library from the version 3.11.8 to 3.12.3 in a Next.js appt that uses @graphql-codegen with client-preset but just upgrading the lib introduced tons of typescript errors. Reading the docs a little it mentions that I have to change the codegen and the client-preset configuration to disable fragment masking and stop using the useFragment function provided by it and use the hook from apollo client instead. But that is proving to be a huge effort in the codebase since we use this function a lot. Is there a way to avoid having to do this but still upgrade apollo client? We want to be able to upgrade Next.js to v15 at some point ad that comes with react 19 so I’m guessing that we’ll need to have apollo client in a version that has react 19 as peer dependency.
You need to do all that only if you want to use the new Apollo Client fragment masking, but you should be able to stay on the Codegen fragment masking as well (although that will just hide some types from you on the TypeScript level, not give you any runtime benefits).
Could you give us some examples of the TS errors you are seeing?
Hey Ienz, thanks for the attention.
Sure can, the following component for example:
import { useQuery } from '@apollo/client'
import { getFragmentData, graphql } from '@/_generated/gql'
const TEST_QUERY = graphql(`
query GetTestUser {
user {
...Test_UserType
}
}
`)
const FRAGMENT_TEST__USER_TYPE = graphql(`
fragment Test_UserType on UserType {
id
firstName
lastName
}
`)
export default function Test() {
const { data } = useQuery(TEST_QUERY)
const user = getFragmentData(FRAGMENT_TEST__USER_TYPE, data)
return <pre>{JSON.stringify(user, null, 2)}</pre>
}
gives me the following error on the line that calls getFragmentData
:
features/_common/components/atoms/Test.tsx:23:58 - error TS2769: No overload matches this call.
The last overload gave the following error.
Argument of type '{ __typename?: "Query" | undefined; user?: { __typename?: "UserType" | undefined; firstName?: string | null | undefined; lastName?: string | null | undefined; id: string; } | null | undefined; } | undefined' is not assignable to parameter of type 'readonly { ' $fragmentRefs'?: { Test_UserTypeFragment: Test_UserTypeFragment; } | undefined; }[] | null | undefined'.
Type '{ __typename?: "Query" | undefined; user?: { __typename?: "UserType" | undefined; firstName?: string | null | undefined; lastName?: string | null | undefined; id: string; } | null | undefined; }' is missing the following properties from type 'readonly { ' $fragmentRefs'?: { Test_UserTypeFragment: Test_UserTypeFragment; } | undefined; }[]': length, concat, join, slice, and 26 more.
23 const user = getFragmentData(FRAGMENT_TEST__USER_TYPE, data)
~~~~
_generated/gql/fragment-masking.ts:54:17
54 export function getFragmentData<TType>(
~~~~~~~~~~~~~~~
The last overload is declared here.
It only starts happening when updating @apollo/client
. I have kept the same versions of @graphql-codegen/cli
(5.0.2), @graphql-codegen/client-preset
(4.3.3), @graphql-codegen/fragment-matcher
(5.0.2) .
@danielbucher My suspicion is that our Unmasked
type is kicking in here, which removes all the $fragmentRefs
and $fragmentName
properties from those generated types. If you inspect data
here, I suspect you won’t see those properties.
To test this theory, could you try passing Masked<GetTestUserQuery>
as a generic argument to the query?
import { useQuery, Masked } from '@apollo/client';
// or whatever the final type name is called
import { GetTestUserQuery } from '@/_generated/gql';
// ...
const { data } = useQuery<Masked<GetTestUserQuery>>(TEST_QUERY);
const user = getFragmentData(FRAGMENT_TEST__USER_TYPE, data);
I believe this will get rid of the TypeScript error.
Assuming the Masked
type works, you might be able to opt into masked types globally which will leave the TData
type alone when it detects $fragmentRefs
. I’ll do some testing on my end to see if this would be a viable solution for those using codegen’s fragment masking feature, but hopefully it should work for now.
As a bit of background with 3.12, we updated all of our query hooks to return MaybeMasked<TData>
as the data
property which will use Unmasked<TData>
if it detects a $fragmentRefs
property in the TData
type without opting in to using masked types.
Let me know if this works for you!
Tried that. The generated type sure is different when using the Masked
generic.
{
__typename?: "Query" | undefined;
user?: ({
__typename?: "UserType";
} & {
" $fragmentRefs"?: {
"Test_UserTypeFragment": Test_UserTypeFragment;
};
}) | null | undefined;
} | undefined
instead of
{
__typename?: "Query" | undefined;
user?: {
__typename?: "UserType" | undefined;
id: string;
firstName?: string | null | undefined;
lastName?: string | null | undefined;
} | null | undefined;
} | undefined
But I still get a (slightly different) error
The last overload gave the following error.
Argument of type '{ __typename?: "Query" | undefined; user?: ({ __typename?: "UserType" | undefined; } & { ' $fragmentRefs'?: { Test_UserTypeFragment: Test_UserTypeFragment; } | undefined; }) | null | undefined; } | undefined' is not assignable to parameter of type 'readonly { ' $fragmentRefs'?: { Test_UserTypeFragment: Test_UserTypeFragment; } | undefined; }[] | null | undefined'.
Type '{ __typename?: "Query" | undefined; user?: ({ __typename?: "UserType" | undefined; } & { ' $fragmentRefs'?: { Test_UserTypeFragment: Test_UserTypeFragment; } | undefined; }) | null | undefined; }' is missing the following properties from type 'readonly { ' $fragmentRefs'?: { Test_UserTypeFragment: Test_UserTypeFragment; } | undefined; }[]': length, concat, join, slice, and 26 more.
24 const user = getFragmentData(FRAGMENT_TEST__USER_TYPE, data)
~~~~
_generated/gql/fragment-masking.ts:54:17
54 export function getFragmentData<TType>(
~~~~~~~~~~~~~~~
The last overload is declared here.
One observation that confuses me here:
Shouldn’t you be passing const user = getFragmentData(FRAGMENT_TEST__USER_TYPE, data.user)
here - focus on the .user
?
Yes, you’re absolutely right. My bad, I built a bad example. Retried and using the Masked
generic worked. I’ll try to opt into the masked types globally now and will report back.
Thanks a lot.
Using the global optin to masked types solved the issue. Sorry for the confusion.
No problem! Glad to know that works. I’ll see if we can put something in our documentation in case others face this same issue. Thanks for confirming!