OneGraph and Graphql Codegen produce enums with numerical representations

After migrating my project's codebase from using the direct Headless Wordpress GraphQL endpoint to OneGraph for Google+Facebook Business support, I encountered an error related to apollo referencing the output codegen. Here is the specific error message:

  graphQLErrors: [
    {
      message: 'Variable "$idTypeFoot" got invalid value 2; Expected type MenuNodeIdTypeEnum.',
      path: [Array],
      extensions: [Object]
    },
    {
      message: 'Variable "$idTypeFoot" got invalid value 2; Expected type MenuNodeIdTypeEnum.',
      path: [Array],
      extensions: [Object]
    }
  ],

The generated/graphql.tsx file contains the output codegen definition as follows:


export enum WordpressMenuNodeIdTypeEnum {
    /** Identify a menu node by the Database ID. */
    DATABASE_ID = 0,
    /** Identify a menu node by the (hashed) Global ID. */
    ID = 1,
    /** Identify a menu node by it's name */
    NAME = 2
}

Prior to the migration to OneGraph, the enum was defined like this:


/** The Type of Identifier used to fetch a single node. Default is "ID". To be used along with the "id" field. */
export enum MenuNodeIdTypeEnum {
    /** Identify a menu node by the Database ID. */
    DatabaseId = 'DATABASE_ID',
    /** Identify a menu node by the (hashed) Global ID. */
    Id = 'ID',
    /** Identify a menu node by its name */
    Name = 'NAME'
}

The dynamic-nav-fields.graphql partial used in the parent query:

fragment DynamicNavFragment on WordpressMenuItem {
    id
    label
    path
    parentId
}

The parent query in dynamic-nav.graphql:

# import DynamicNavFragment from './Partials/dynamic-nav-fields.graphql'

query DynamicNav(
  $idHead: ID!
  $idTypeHead: WordpressMenuNodeIdTypeEnum!
  $idFoot: ID!
  $idTypeFoot: WordpressMenuNodeIdTypeEnum!
    ) {
    Header: wordpress {
        menu(id: $idHead, idType: $idTypeHead) {
            menuItems(where: { parentId: 0 }) {
                edges {
                    node {
                        ...DynamicNavFragment
                        childItems {
                            edges {
                                node {
                                    ...DynamicNavFragment
                                    childItems {
                                        edges {
                                            node {
                                                ...DynamicNavFragment
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    Footer: wordpress {
        menu(id: $idFoot, idType: idTypeFoot) {
            menuItems(where: { parentId: 0 }) {
                edges {
                    node {
                        ...DynamicNavFragment
                        childItems {
                            edges {
                                node {
                                    ...DynamicNavFragment
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

The configuration in codegen.yml file is set up like this:

overwrite: true
schema:
  ${WORDPRESS_API_URL_YML}:
    headers:
      Authorization: Bearer ${WORDPRESS_AUTH_REFRESH_TOKEN_YML}
documents: 'graphql/**/*.graphql'
generates:
  graphql/generated/graphql.tsx:
    plugins:
      - typescript:
          constEnums: false
          enumsAsTypes: false          
          numericEnums: true
          futureProofEnums: false
          enumsAsConst: false
          onlyOperationTypes: false
          maybeValue: T | null | undefined
          noExport: false
          enumPrefix: true
          fieldWrapperValue: T
          wrapFieldDefinitions: true
          skipTypename: false
          nonOptionalTypename: false
          useTypeImports: false
          avoidOptionals: true
          declarationKind: 
            input: interface
            type: interface
      - typescript-operations:
          declarationKind:
            input: interface
            type: interface
          avoidOptionals: true
          exportFragmentSpreadSubTypes: true
      - typescript-react-apollo:
          addDocBlocks: true
          reactApolloVersion: 3
          documentMode: documentNodeImportFragments
    config:
      maybeValue: T | null | undefined
      declarationKind:
        input: interface
        type: interface
      documentNodeImportFragments: true
      reactApolloVersion: 3
      withHooks: true
      withHOC: false
      avoidOptionals: true
      withComponent: false
      exportFragmentSpreadSubTypes: true
      addDocBlocks: true
  graphql/graphql.schema.graphql:
    plugins:
      - schema-ast
    config:
      commentDescriptions: true
  graphql/graphql.schema.json:
    plugins:
      - introspection
    config:
      commentDescriptions: true
      
hooks:
  afterAllFileWrite: 
    - prettier --write

I am seeking assistance in resolving why OneGraph replaces the generated Enum values with numbers that Apollo client cannot read. Any insights on how to address this issue would be greatly appreciated.

Answer №1

In short, the solution is to modify the codegen.yml file and set numericEnums: false:

generates:
  graphql/generated/graphql.tsx:
    plugins:
      - typescript:
          numericEnums: false

To troubleshoot this issue, I analyzed the GraphQL SDL definition of WordpressMenuNodeIdTypeEnum:

enum WordpressMenuNodeIdTypeEnum {
    # Identify a menu node by the Database ID.
    DATABASE_ID
    # Identify a menu node by the (hashed) Global ID.
    ID
    # Identify a menu node by its name
    NAME
}

I compared this with the previous setting of numericEnums: true in codegen.yml, and utilized them on the graphql playground at https://i.stack.imgur.com/OGGkv.png

The screenshot illustrates that the SDL definition was transformed into numeric enums (the standard for typescript enums at runtime)

I then experimented with numericEnums: false: https://i.stack.imgur.com/aNxtT.png

The resulting output reflects the expected behavior, where WordpressMenuNodeIdTypeEnum.Name is equivalent to "NAME", ensuring Apollo obtains the anticipated value rather than the runtime typescript-enum value (2).

Answer №2

UPDATE — shoutout to @sgrove for his amazing help in troubleshooting the problem!

So, I encountered two possible solutions: either overwrite each enum converted from a string value representing the __type before or hardcode these values in. To access the custom enum value, I needed to include the following in my codegen.yml:

config:
    enumValues:
       WordpressMenuNodeIdTypeEnum: '@/types/global-enums#WordpressMenuNodeIdTypeEnum'

This directs the generated code to import the enum definition from @/types/global-enums.ts.

/** Different types of identifier used to fetch a single node. Default is "ID". Should be used with the "id" field. */
export enum WordpressMenuNodeIdTypeEnum {
    /** Identifies a menu node by the Database ID. */
    DatabaseId = 'DATABASE_ID',
    /** Identifies a menu node by the (hashed) Global ID. */
    Id = 'ID',
    /** Identifies a menu node by its name */
    Name = 'NAME'
}

But then, I faced another issue and opted to directly hardcode the NAME value for WordpressMenuNodeIdTypeEnum into my graphql schema:

# import DynamicNavFragment from './Partials/dynamic-nav-fields.graphql'

query DynamicNav($idHead: ID!, $idFoot: ID!) {
    Header: wordpress {
        menu(id: $idHead, idType: {@NAME@}) {
            menuItems(where: { parentId: 0 }) {
                edges {
                    node {
                        ...DynamicNavFragment
                        childItems {
                            edges {
                                node {
                                    ...DynamicNavFragment
                                    childItems {
                                        edges {
                                            node {
                                                ...DynamicNavFragment
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    Footer: wordpress {
        menu(id: $idFoot, idType: {@NAME@}) {
            menuItems(where: { parentId: 0 }) {
                edges {
                    node {
                        ...DynamicNavFragment
                        childItems {
                            edges {
                                node {
                                    ...DynamicNavFragment
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Subsequently, I encountered an additional challenge with the apollo cache since both ServicesTopQuery and DynamicNavQuery had a shared top-level wordpress field with the corresponding __typename "WordPressRootQuery."

Here are the codegen definitions for ServicesTopQuery, ServicesTopQueryVariables, DynamicNavQuery, and DynamicNavQueryVariables:


// Codegen definition for ServicesTopQuery and ServicesTopQueryVariables

[...]

And here is some code from pages/index.tsx where DynamicNavQuery data was initially populated but swiftly overwritten by ServicesTopQuery data.

[...]

To address this issue, I consulted the Apollo docs and updated the instantiation of the new InMemoryCache class with the following configuration:

cache: new InMemoryCache({
            typePolicies: {
                Query: {
                    fields: {
                        wordpress: {
                            merge(existing, incoming, { mergeObjects }) {
                                // invoking nested merge functions
                                return mergeObjects(existing, incoming);
                            }
                        }
                    }
                }
            }
        })
    });

By implementing the above change, I was able to prevent existing wordpress field data from being overwritten by incoming wordpress data, effectively resolving the issue at hand. The migration process from a headless WordPress endpoint to a OneGraph endpoint turned out to be more complex than expected, so I wanted to provide a detailed explanation in case others encounter a similar situation in the future.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Set up linter rules that utilize `useEffect` in place of `React.useEffect` (utilizing named export rather than full export)

It's important in our codebase to always use: import { useEffect } from 'react'; instead of: import React from 'react'; // use in code as `React.useEffect` Can we enforce this rule with longer eslint rules? If so, how can we impl ...

Customizable mongoDB database collection

Is there a more efficient way to make calls to different collections based on a function parameter? I'm exploring the possibility and if it's not feasible, I'll handle it case by case. Currently, I have this code and the goal is to have a u ...

Using import statement is mandatory when loading ES Module in TS Node: server/src/index.ts

Attempting to kickstart a TypeScript Node project, I've gone ahead and added some essential dependencies (TypeScript, ESLint, Mongoose, and GraphQL). However, upon executing the command: ts-node-dev --respawn --transpile-only src/index.ts An error me ...

Ways to invoke a slice reducer from a library rather than a React component

I've been working on a React application using the React Boilerplate CRA Template as my foundation. This boilerplate utilizes Redux with slices, which is great for updating state within a React component. However, I'm facing a challenge when try ...

A guide to mocking Prisma using Jest mock functionality

Utilizing prisma for database interactions and eager to implement jest-mock to simulate the findMany call. https://jestjs.io/docs/jest-object#jestmockedtitem-t-deep--false brands.test.ts import { PrismaService } from "@services/mysql.service"; i ...

Error message: Parameters are not defined in the Next.js

Struggling to retrieve the endpoint from a specific page in my Next.js application using URL parameters. Despite being able to view the endpoint in the browser, it keeps returning as undefined. I attempted utilizing usePathname from next/navigation, which ...

Is there a way to modify the code so that upon refreshing, only the randomly selected 20 items are displayed without showing the ordered items?

Recently, I posed a query and sought assistance on getting 20 random items from a specific JSON file. After implementing one of the suggested solutions, here is the script that I employed: const data = Myjson; useEffect(() => { for (let i = data ...

Having trouble getting Tailwind CSS utility classes to work with TypeScript in create-react-app

I have been struggling to troubleshoot this issue. I developed a React application with TypeScript and integrated Tailwind CSS following the React setup guidelines provided on the official Tailwind website here. Although my code and configuration run succ ...

Exploring domain routing in Next.js with sub-domains

I am currently working on translating my Next app according to the specific subdomain. For instance, I want en.hello.com to display content in English, while it.hello.com should show content in Italian. I have been attempting to accomplish this using Next ...

Ways to turn off a TypeScript error across the entire project

Due to an unresolved TypeScript bug causing a false positive, I am looking to disable a specific TypeScript error for my entire project. How can this be achieved? The requirements are: Disabling only one type of error Not limited to a single line or file ...

Elementary component placed in a single line

Creating a text dropdown menu using the following code: import { Autocomplete, TextField } from '@mui/material' import React, { useState } from 'react' const options = [ 'Never', 'Every Minute', 'Every 2 ...

Troubleshooting problem with TypeScript and finding/filtering operations

let access = environment.access.find(it => it.roleName == userRole); Property 'filter' does not exist on type '{ siteadmin: string[]; manager: string[]; employee: string[]; contractor: any[]; }'. This scenario should work perfectly ...

Steps for sorting items from a list within the past 12 hours

I'm currently working with Angular and I have data in JSON format. My goal is to filter out items from the last 12 hours based on the "LastSeen" field of the data starting from the current date and time. This is a snippet of my data: { "Prod ...

Unexpected Secondary Map Selector Appears When Leaflet Overlay is Added

Working on enhancing an existing leaflet map by adding overlays has presented some challenges. Initially, adding different map types resulted in the leaflet selector appearing at the top right corner. However, when attempting to add LayerGroups as overlays ...

The redirect feature in getServerSideProps may not function properly across all pages

Whenever a user visits any page without a token, I want them to be redirected to the /login page. In my _app.js file, I included the following code: export const getServerSideProps = async () => { return { props: {}, redirect: { des ...

Unsuitable data types in GraphQL - is the configuration faulty?

I'm currently facing an issue that's giving me some trouble. In my datamodel.prisma, I added a new type called Challenge: type Challenge { id: ID! @id createdAt: DateTime! @createdAt updatedAt: DateTime! @updatedAt completed: Bo ...

Messages are not being emitted from the socket

I've been encountering an issue with message transmission from client to server while using React and ExpressJS. When I trigger the sendMessage function on the client side, my intention is to send a message to the server. However, for some reason, the ...

Retrieving data from notifications without having to interact with them (using Firebase Cloud Messaging and React Native)

Currently, I am able to handle messages whether the app is open, minimized, or closed. However, how can I process a message if a user logs into the application without receiving a notification? useEffect(() => { messaging().setBackgroundMessageHa ...

The art of crafting informative error log messages in Protractor using TypeScript

I am currently utilizing Protractor, written in typescript, to conduct tests on a live website. I am seeking guidance on how to generate log messages when a Protractor test fails. Presently, the only feedback received is a simple YES/NO message, as shown b ...

Exploring the getServerSideProps function in Next.js with a loop

I'm currently diving into the world of Next.js and facing an issue while trying to retrieve data from the database and display it on the index page. The problem arises when I execute the getServerSideProps function, as it seems to enter into a loop an ...