graphql-code-generator-community icon indicating copy to clipboard operation
graphql-code-generator-community copied to clipboard

Apollo v4 CodeGen Broken w/o config Overrides

Open ksylvest opened this issue 3 months ago • 21 comments

Which packages are impacted by your issue?

@graphql-codegen/typescript-react-apollo

Describe the bug

When using @graphql-codegen with the typescript-react-apollo plugin and @apollo/client v4 the generated code is invalid. Generated hooks (e.g. useQuery / useMutation) must be imported from @apollo/client/react, but are imported through @apollo/client. This causes a generated file to compile.

A workaround is to manually specify the following in the codegen.ts file:

config: {
  apolloReactCommonImportFrom: "@apollo/client/react",
  apolloReactHooksImportFrom: "@apollo/client/react",
}

Your Example Website or App

N/A

Steps to Reproduce the Bug or Issue

  1. Bump @apollo/client from v3 to v4
  2. Run codegen
  3. Build the generated code.

Expected behavior

The file does not compile and it is expected to compile.

Screenshots or Videos

No response

Platform

  • OS: macOS
  • NodeJS: v24

Codegen Config File

No response

Additional context

No response

ksylvest avatar Sep 03 '25 20:09 ksylvest

Hello ! I encountered the same issue after upgrading to apollo v4 but I haven't succeed to resolve it like you describe. I got some other types with invalid import after code generation.

Type error: Namespace '"./node_modules/@apollo/client/react/index"' has no exported member 'MutationFunction'.

@ksylvest did you succeed to solve it ? Thanks

I really hope that a new version of the plugin will be release soon :)

mchoraine avatar Sep 04 '25 10:09 mchoraine

@mchoraine I hadn't tested w/ a schema containing mutations. I just re-tested you are correct those types don't appear to exist anymore. I'm not sure how to solve.

ksylvest avatar Sep 04 '25 16:09 ksylvest

Has anyone picked this up already? Bump @apollo/client from v3 to v4

Otherwise I can maybe take a look

vriesr032 avatar Sep 05 '25 08:09 vriesr032

Hello ! I encountered the same issue after upgrading to apollo v4 but I haven't succeed to resolve it like you describe. I got some other types with invalid import after code generation.

Type error: Namespace '"./node_modules/@apollo/client/react/index"' has no exported member 'MutationFunction'.

@ksylvest did you succeed to solve it ? Thanks

I really hope that a new version of the plugin will be release soon :)

Query types have been moved into type namespaces; https://www.apollographql.com/docs/react/migrating/apollo-client-4-migration#namespaced-types

MutationFunction should be located in the useMutation namespace, you could try useMutation.MutationFunction.

Netail avatar Sep 05 '25 08:09 Netail

Hi all 👋

Apollo Client v4 and generated hooks incompatibility goes beyond type renames. v4 introduces lots of type overloads that do NOT work with generated hooks, creating a real risk of runtime errors in production.

You can see an example of wrong type inference here.

Apollo team recommends against using generated hooks for this reason.

We (Codegen team) recommend migrating to Client Preset instead of relying generated hooks. We will deprecate typescript-react-apollo plugin soon.

Here's a codemod that you could use for migration: https://www.npmjs.com/package/@eddeee888/gcg-operation-location-migration . Please let me know if you have any issues.

  • Blog post: https://the-guild.dev/graphql/hive/blog/graphql-codemod-use-case-migrating-from-apollo-client-generated-hooks-to-client-preset
  • Video: https://www.youtube.com/watch?v=GGmt0hvnQNU

eddeee888 avatar Sep 05 '25 11:09 eddeee888

I don't think my team would be glad to review few thousand lines of changes with migration from codegen to client preset 😅.

In my case bundle size and strictness of dataState property isn't really an issue. Wouldn't want to stockpile techdebt either so would be nice to have some stopgap option where it generates with uncertain dataState. I'll try to look into it in spare time

EvgeniyKumachev avatar Sep 09 '25 13:09 EvgeniyKumachev

Logic for visitor.ts doesn't seem that hard to upgrade, i've mostly updated it to support v4 https://gist.github.com/EvgeniyKumachev/c65fdd9092b1a1d09132f700774c238e

Since in v4 they removed hocs and components options for them should probably be deprecated or removed but i'm not sure about deprecating them myself

EvgeniyKumachev avatar Sep 09 '25 21:09 EvgeniyKumachev

Hi @EvgeniyKumachev ,

I appreciate the time you put into trying to fix this. 🙂

I don't think my team would be glad to review few thousand lines of changes with migration from codegen to client preset 😅.

I totally understand the concern, I highly recommend doing this in multiple PRs. You can target where you'd like to run the migrations by changing a generates blocks' key. Hopefully, there are tests in place to catch potential errors.

In my case bundle size and strictness of dataState property isn't really an issue. Wouldn't want to stockpile techdebt either so would be nice to have some stopgap option where it generates with uncertain dataState

Unfortunately, we cannot enforce this expectation for all users 😕 I'd say the stopgap is the incremental codemod approach mentioned above.

eddeee888 avatar Sep 10 '25 11:09 eddeee888

Hi @EvgeniyKumachev ,

I appreciate the time you put into trying to fix this. 🙂

I don't think my team would be glad to review few thousand lines of changes with migration from codegen to client preset 😅.

I totally understand the concern, I highly recommend doing this in multiple PRs. You can target where you'd like to run the migrations by changing a generates blocks' key. Hopefully, there are tests in place to catch potential errors.

In my case bundle size and strictness of dataState property isn't really an issue. Wouldn't want to stockpile techdebt either so would be nice to have some stopgap option where it generates with uncertain dataState

Unfortunately, we cannot enforce this expectation for all users 😕 I'd say the stopgap is the incremental codemod approach mentioned above.

Got it, thanks. I'll make a separate fork then with support for v4 as described above. Sadly i can't justify to my PM refactor of the whole codebase to support strict typing of one property that we don't even use

EvgeniyKumachev avatar Sep 10 '25 11:09 EvgeniyKumachev

Hey all! If your repo is using typescript and react, I've found that it's unnecessary to run the migration script from @eddeee888. His script will copy your GraphQL queries into you components. However, you can access those from the gql/graphql.ts file generated by the client-preset. This is a pretty good guide to understand client-preset.

Here's what I did instead.

// codegen.ts
// I kept both generated files so I could incrementally migrate my files. When I'm ready to migrate to v4, I'll remove the original.

const config: CodegenConfig = {
  ...
  ignoreNoDocuments: true,
  generates: {
    ["./app/generated.ts"]: { // This is my original file that is generating the QueryHooks
      plugins: [
        "typescript",
        "typescript-operations",
        "typescript-react-apollo",
        "named-operations-object",
      ],
    },
    "./app/gql/": { // These is my new generated files.
      preset: "client",
      presetConfig: {
        fragmentMasking: false,
      },
    },
  },
  ...
}
// SupportBanner.tsx

import {
  BannerPreferencesDocument,
  BannerPreferencesQuery,
  DismissSupportBannerDocument,
  DismissSupportBannerMutation,
  SupportPreferenceCategoryEnum,
} from "gql/graphql";

const SupportBanner: React.FC<SupportBannerProps> = ({
  topic,
  ...viewProps
}): React.ReactElement | null => {
  const { data, error, loading } = useQuery<BannerPreferencesQuery>(
    BannerPreferencesDocument,
  );
  const [dismissBanner] = useMutation<DismissSupportBannerMutation>(
    DismissSupportBannerDocument,
  );

AELSchauer avatar Sep 10 '25 18:09 AELSchauer

Oh no! Seems like our huge codebase is now stuck at Apollo v3 :( The static hook generation is a massively integral part of our app. We have also developed a schema versioning system with static assurence above it, to ensure our 30+ projects always run with compatible API. All our GQL documents are in own .gql files. This sped up intellisense speed a lot, not having to parse gql from ts files. We have a huge schema. It was a big migration, I am definitely not going back. What would it take for codegen to support Apollo v4? We might consider forking it.

vitexikora avatar Sep 15 '25 10:09 vitexikora

Hi @vitexikora , I'm curious about the performance issue you mention:

This sped up intellisense speed a lot, not having to parse gql from ts files

May I know if you were using Client Preset when you see this issue? If so, could you please elaborate which part of intellisense speed was impacted? The document node type? Codegen run duration ? Or something else?

What would it take for codegen to support Apollo v4? We might consider forking it.

I can't think of a way to preserve Apollo Client's type overloads using the generated hooks approach. Note that dataState type overload is one example introduced in v4, and there could be more that I'm not aware of, and may increase in the future

i.e. the effort to maintain type correctness for generated hooks would increase exponentially

eddeee888 avatar Sep 16 '25 14:09 eddeee888

How software developers just LOVE to break backward compatibility (looking at you, React Router). Like some of the people who commented above, we also have quite a large codebase with generated hooks. So thanks to the Apollo developers - since we’ll have to rewrite and refactor everything anyway, we can now take the opportunity to consider some alternatives lol

e965 avatar Oct 16 '25 20:10 e965

May I know if you were using Client Preset when you see this issue? If so, could you please elaborate which part of intellisense speed was impacted? The document node type? Codegen run duration ? Or something else?

@eddeee888 The whole intellisense experience was impacted. Even waiting for a component name to import or using ctrl+click. It was somehow being blocked centrally by GQL type parsing of some sort. I have spent several hours on several occasions trying to get around this, because it was a big burden. However, it was difficult to reliable simulate. But specifying in .graphqlrc to walk through only *.gql files worked like a magic trick. So I rewrote the rest of gql segments to own files.

the effort to maintain type correctness for generated hooks would increase exponentially

So the only issue is higher maintenance. I mean if it meant to have some any types here and there but the main thing would work, I would really gladly take it. This principle of using generated hooks is really good, it has many upsides. I don't want to 'downgrade' our app.

we can now take the opportunity to consider some alternatives lol

@e965 I feel you. We just survived going RR5 -> RR7, took a week, lot of swearing, and we lost some blood (notably regex paths). But I refused to migrate ESLint, there was always this level of hate we shared against this slow plugin-overloaded thing, so when they announced the big BC break, we switched to Biome. I was missing some stuff for a while, but I am never looking back :)

vitexikora avatar Oct 17 '25 19:10 vitexikora

Thanks for the details @vitexikora !

The whole intellisense experience was impacted. Even waiting for a component name to import or using ctrl+click. It was somehow being blocked centrally by GQL type parsing of some sort. I have spent several hours on several occasions trying to get around this, because it was a big burden. However, it was difficult to reliable simulate. But specifying in .graphqlrc to walk through only *.gql files worked like a magic trick. So I rewrote the rest of gql segments to own files.

If I understood this correctly, you were using some like this for document path: documents: 'src/**/*.ts' which was slow. Then you switched to documents: 'src/**/*.gql' and it was much faster.

If that was the case, most likely Intellisense was reading too many .ts files. I suspect you'd have the same performance if you narrowed down your file pattens to something like documents: 'src/**/*.graphql.ts' with .graphql.ts files have GraphQL documents and follow either co-located or near-opeartion-file patterns.

This principle of using generated hooks is really good, it has many upsides. I don't want to 'downgrade' our app.

Could you please help me understand what you like about generated hooks? 🙂

eddeee888 avatar Oct 17 '25 22:10 eddeee888

I suspect you'd have the same performance if you narrowed down your file pattens (...)

I thought that too, but when I was testing this some time ago, it still had some issues. Then I tried using only *.gql and it was so much better that I have instantly followed that path. The client-preset is not compatible with gql files?

Could you please help me understand what you like about generated hooks? 🙂

Well now that I have got time to dig deeper in it, the functions that we had built above generated hooks may possibly work with client-preset as well. I may have been misled by some posts hating on the client-preset.

vitexikora avatar Oct 22 '25 08:10 vitexikora

I thought that too, but when I was testing this some time ago, it still had some issues. Then I tried using only *.gql and it was so much better that I have instantly followed that path. The client-preset is not compatible with gql files?

Client Preset recommends operation and fragment co-location which means these should live in the same file as your component.

.graphql/.gql files works, but the DX is a bit awkward because that's not approach it is built for.

eddeee888 avatar Oct 25 '25 11:10 eddeee888

Client Preset recommends operation and fragment co-location which means these should live in the same file as your component.

What about a scenario, when there is a modular system that defines some core GQL types, like File, Image, PublicUser, etc. And then there are custom modules that use them, eg. news { id, author { ...PublicUser } }. Our modules are decoupled from the core, they only share some core types. So an operation lives in something like src/modules/News/gql/documents/*.gql, News related fragments are nearby, but core types are in src/core/graphql/fragments/*.gql. Is this feasible with Client Preset?

.graphql/.gql files works, but the DX is a bit awkward because that's not approach it is built for.

In what ways? Is there a comparison of both?

vitexikora avatar Oct 25 '25 11:10 vitexikora

What about a scenario, when there is a modular system that defines some core GQL types, like File, Image, PublicUser, etc. And then there are custom modules that use them, eg. news { id, author { ...PublicUser } }. Our modules are decoupled from the core, they only share some core types. So an operation lives in something like src/modules/News/gql/documents/.gql, News related fragments are nearby, but core types are in src/core/graphql/fragments/.gql. Is this feasible with Client Preset?

Yes, you could split GraphQL docs from components if required. Here's an example:

// src/modules/News/gql/documents/News.gql.ts
import { graphql } from 'src/gql';
export const NewsDoc = graphql(`
  query news {
    id
    author { 
      ...PublicUser 
    }
  }
`)

// src/core/graphql/fragments/PublicUser.gql.ts
import { graphql } from 'src/gql';

export const PublicUserFragmentDoc = graphql(`
  query news {
    fragment PublicUser on User {
      id
    }
  }
`);

// src/YourComponent.tsx
import { useQuery } from '@apollo/client/react';
import { NewsDoc } from from 'src/modules/News/gql/documents/News.gql';

export const YourComponent = () => {
  useQuery(NewsDoc);
}

In what ways? Is there a comparison of both?

Client Preset uses graphql(...) function as GraphQL doc markers (same as .gql ) AND to get the document doc (wired up in hooks, or manually imported from generated files), . So if you updated the content of .gql files, you'd have to update graphql(...) content as well

eddeee888 avatar Oct 26 '25 03:10 eddeee888

I kind of like the clarity and tidiness of pure .gql files, after I migrated all projects to use them exclusively. So I would have to run some kind of a command after updating them? (Just like I do now with generated hooks?).

Thank you for your explanations, I am sure it will help many developers considering the migration.

vitexikora avatar Oct 30 '25 23:10 vitexikora

So I would have to run some kind of a command after updating them? (Just like I do now with generated hooks?).

That's right, you'd run codegen to generate types for your documents. However, instead of importing generated hooks, you'd use the document result in your TS file directly.

Here's the Client Preset guide

eddeee888 avatar Nov 02 '25 09:11 eddeee888