14

Sorry if this is a bit involved, but I'm really trying to close the last mile on being able to use Apollo Client for local as well as server state, with automatic Typescript everywhere. To wit, I have a query like this:

query NavigationBarQuery($userId: Int, $portfolioCompanyId: Int!) {
  user(id: $userId) {
    id
    firstName
    lastName
    company {
      ... on CompanyInterface {
        companyType
      }
    }
  }
}

That is being imported by my NavigationBar component like so:

import { NavigationBarQuery, NavigationBarQueryVariables } from '../../../graphql/generated/NavigationBarQuery';
import NAVIGATION_BAR_QUERY from '../../../graphql/NavigationBarQuery.graphql';

const NavigationBar = (vars: NavigationBarQueryVariables) => (
  <Query query={NAVIGATION_BAR_QUERY} variables={vars}>
    {({ loading, error, data, client }: QueryResult<INavigationBarClientQuery>) => {

     // etc.

Generation is performed with a local schema file (dumped from Graphene) like so:

apollo client:codegen --localSchemaFile ./build/schema.json --includes './src/graphql/**' --target typescript

This works great, I get TypeScript types and everything.

However, I'd like to include some local state, with a query like this:

query NavigationBarQuery($userId: Int, $portfolioCompanyId: Int!) {
  user(id: $userId) {
    id
    firstName
    lastName
    company {
      ... on CompanyInterface {
        companyType
      }
    }
  }
  showNavbarUserInfo @client
}

This query works just fine if I bypass TypeScript, but when I attempt to generate Typescript definitions for it, the generation script emits this error:

.../client/src/graphql/NavigationBarQuery.graphql: Cannot query field "showNavbarUserInfo" on type "Query".
{ ToolError: Validation of GraphQL query document failed
    at Object.validateQueryDocument (/Users/gavin/.config/yarn/global/node_modules/apollo-language-server/lib/errors/validation.js:32:19)
    at Object.generate [as default] (/Users/gavin/.config/yarn/global/node_modules/apollo/lib/generate.js:19:18)
    at write (/Users/gavin/.config/yarn/global/node_modules/apollo/lib/commands/client/codegen.js:64:54)
    at Task.task (/Users/gavin/.config/yarn/global/node_modules/apollo/lib/commands/client/codegen.js:83:46) name: 'ToolError' }

What, if anything, is the workaround for this? As per usual, looking for examples.

Gavin
  • 6,495
  • 3
  • 21
  • 22

3 Answers3

8

Providing an updated answer here because @cnp's answer is missing examples and contains broken links.

My examples use the new Apollo CLI (https://github.com/apollographql/apollo-tooling) with npx, although it can also be installed locally and used without the npx command.

You need to extend your schema with local-only fields by adding a new SDL file. Using the example queries from the original question, you would add a file (e.g. schema.graphql) with the following content:

extend type NavigationBar {
  showNavbarUserInfo: Boolean!
}

Optional: if you're using VS Code's Apollo GraphQL extension, now would be a good time to let it know where to find the new SDL file by adding it to the includes properly in apollo.config.js:

module.exports = {
  client: {
    includes: ['./schema.graphql'],
  },
};

Now include the showNavbarUserInfo @client local field in one of your queries, then run your code gen command to include your original schema.json file and your new schema.graphql file:

npx apollo codegen:generate --localSchemaFile=schema.json,schema.graphql --target=typescript --includes=src/**/*.tsx --tagName=gql --addTypename --globalTypesFile=src/types/global-types.ts types
Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275
8

I just worked through this so hopefully this will help someone. And if your name actually is "Someone" and you just happen to be looking for this information; this IS totally for you!

My setup:

I have two schema files.

  • One that is pulled down form my GraphQL Server
  • A local schema file that contains the extended properties for storing values locally

Dependencies

  • @apollo/client": "^3.3.11"
  • @apollo/react-hooks": "^4.0.0"

DevDependencies

  • @graphql-codegen/add": "^2.0.2"
  • @graphql-codegen/cli": "^1.20.1"
  • @graphql-codegen/typescript-apollo-client-helpers": "^1.1.2"
  • @graphql-codegen/typescript-operations": "^1.17.14"
  • @graphql-codegen/typescript-react-apollo": "^2.2.1"
  1. For better usage, I recommend creating a codegen.yml file (unless you've watched a lot of hacker movies and think it's WAY more cool to use the console for all these options. Or, if you just want to... I'm not judging.)
#./codegen.yml
# See: https://graphql-code-generator.com/docs/getting-started/codegen-config
overwrite: true
schema:
  - "https://my-dev-server.com/v1/graphql":
      headers:
        'Authorization': 'Bearer [TOKEN]'
generates:
  graphql/generated/generated.ts:
    # This is the defined client side schema file. It will first load the root schema defined above, and then load this (see: https://graphql-code-generator.com/docs/getting-started/schema-field#local-graphql-files)
    schema: graphql/client-schema.graphql
    # This is where the gql queries/subscripts/mutations reside
    documents: "graphql/queries/**/*.ts"
    plugins:
      - add:
          content: /* eslint-disable */
      - typescript
      - typescript-operations
      - typescript-react-apollo
    config:
      withHooks: true

  1. Setup local schema file. Call it what you like, just make sure you set the value correctly in the codegen.yml. I've called mine client-schema.graphql since I'm super clever....
#./client-schema.graphql

# see: https://www.apollographql.com/docs/react/local-state/managing-state-with-field-policies/
directive @client on FIELD

type LocalStateType {
  companyId: String
  contactId: String
}

extend type Users {
  localState: LocalStateType!
}
  1. Once the above is in place and depending on your IDE, intellisense should start picking up the changes. You will need to use the @client directive on your query(s); like so:
export const QUERY_USER_BY_ID = gql`
  query getUserDetails($Id: uuid!) {
    Users_by_pk(Id: $Id) {
      EmailAddress
      Id
      localState @client {
        companyId
        contactId
      }
    }
  }
`;
  1. Run the command to generate the goods:
  • npx graphql-codegen --config codegen.yml

KEY NOTES

  • I've noticed that there is no errors displayed if you set the path incorrectly for the client (client-schema.graphql) file. However, you'll know you have it wrong if you don't see the generated data in the output referencing the @client data.

References:

anAgent
  • 2,550
  • 24
  • 34
  • `directive @client on FIELD` is a nice one that I wouldn't have known about without your answer. – x1a4 Sep 01 '21 at 20:33
  • Also works with `graphql-let`. Only difference is point to the `client-schema.graphql` in `graphql-let.yml` vs `codegen.yml`. – Tim Scott Apr 28 '23 at 16:32
  • @anAgent Thank you so much. Spent hours on this and your solution finally solves my problem – yataw Aug 19 '23 at 08:23
1

In order for this to work you need to define a .graphql SDL file with your client local state types. The codegen feature will then see this file and merge it in with your main schema. See https://github.com/apollographql/apollo-tooling/issues/949#issuecomment-459907307 for more info, and this link for an example: https://github.com/apollographql/fullstack-tutorial/blob/master/final/client/src/resolvers.js#L4. Note the use of extends Query.

cnp
  • 1,010
  • 2
  • 9
  • 20