Presets
client-preset

Client preset

Package nameWeekly DownloadsVersionLicenseUpdated
@graphql-codegen/client-preset (opens in a new tab)DownloadsVersionLicenseFeb 7th, 2023

Installation

Terminal
yarn add -D @graphql-codegen/client-preset

The client-preset provides typed GraphQL operations (Query, Mutation and Subscription) by perfectly integrating with your favorite GraphQL clients:

  • React

    • @apollo/client (since 3.2.0, not when using React Components (<Query>))
    • @urql/core (since 1.15.0)
    • @urql/preact (since 1.4.0)
    • urql (since 1.11.0)
    • graphql-request (since 5.0.0)
    • react-query (with graphql-request@5.x)
    • swr (with graphql-request@5.x)
  • Vue

    • @vue/apollo-composable (since 4.0.0-alpha.13)
    • villus (since 1.0.0-beta.8)
    • @urql/vue (since 1.11.0)

If your stack is not listed above, please refer to our framework/language specific plugins in the left navigation.

Getting started

For step-by-step instructions, please refer to our dedicated guide.

Config API

The client preset allows the following config options:

  • scalars: Extends or overrides the built-in scalars and custom GraphQL scalars to a custom type.
  • strictScalars: If scalars are found in the schema that are not defined in scalars an error will be thrown during codegen.
  • namingConvention: Available case functions in change-case-all are camelCase, capitalCase, constantCase, dotCase, headerCase, noCase, paramCase, pascalCase, pathCase, sentenceCase, snakeCase, lowerCase, localeLowerCase, lowerCaseFirst, spongeCase, titleCase, upperCase, localeUpperCase and upperCaseFirst
  • useTypeImports: Will use import type {} rather than import {} when importing only types. This gives compatibility with TypeScript's "importsNotUsedAsValues": "error" option.
  • skipTypename: Does not add __typename to the generated types, unless it was specified in the selection set.
  • enumsAsTypes: Generates enum as TypeScript string union type instead of an enum. Useful if you wish to generate .d.ts declaration file instead of .ts, or if you want to avoid using TypeScript enums due to bundle size concerns
  • arrayInputCoercion: The GraphQL spec (opens in a new tab) allows arrays and a single primitive value for list input. This allows to deactivate that behavior to only accept arrays instead of single values.

For more information or feature request, please refer to the repository discussions (opens in a new tab).

Fragment Masking

As explained in our guide, the client-preset comes with Fragment Masking enabled by default.

This section covers this concept and associated options in details.

Embrace Fragment Masking principles

Fragment Masking helps expressing components data-dependencies with GraphQL Fragments.

By doing so, we ensure that the tree of data is properly passed down to the components without "leaking" data. It also allow to colocate the Fragment definitions with their components counterparts:

src/Film.tsx
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
 
export const FilmFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
const Film = (props: { film: FragmentType<typeof FilmFragment> }) => {
  const film = useFragment(FilmFragment, props.film)
  return (
    <div>
      <h3>{film.title}</h3>
      <p>{film.releaseDate}</p>
    </div>
  )
}
 
export default Film

For a deeper and more visual explanation of Fragment Masking, please refer to Laurin (opens in a new tab)'s article: Unleash the power of Fragments with GraphQL Codegen (opens in a new tab)

For a introduction on how to design your GraphQL Query to leverage Fragment Masking, please refer to our guide.

The FragmentType<T> type

As explained in our guide, the top-level GraphQL Query should include the fragment (...FilmItem) and pass down the data to child components.

At the component props definition level, the FragmentType<T> type ensures that the passed data contains the required fragment (here: FilmFragment aka FilmItem in GraphQL).

src/Film.tsx
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
 
export const FilmFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
const Film = (props: {
  /* the passed `film` property contains a valid `FilmItem` fragment 🎉 */
  film: FragmentType<typeof FilmFragment>
}) => {
  const film = useFragment(FilmFragment, props.film)
  return (
    <div>
      <h3>{film.title}</h3>
      <p>{film.releaseDate}</p>
    </div>
  )
}
 
export default Film
⚠️

FragmentType<T> is not the Fragment's type

A common misconception is too mix FragmentType<T> and the Fragment's type. You might need to get the Fragment's type directly, for example, for helper functions or testing. In this scenario, you will need to import it from the generated files, as described in the next section.

The useFragment() helper

The useFragment() function helps narrowing down the Fragment type from a given data object (ex: film object to a FilmFragment object):

src/Film.tsx
import { FragmentType, useFragment } from './gql/fragment-masking'
import { graphql } from '../src/gql'
 
export const FilmFragment = graphql(/* GraphQL */ `
  fragment FilmItem on Film {
    id
    title
    releaseDate
    producers
  }
`)
 
const Film = (props: { film: FragmentType<typeof FilmFragment> }) => {
  const film = useFragment(FilmFragment, props.film)
  // `film` is of type `FilmItemFragment` 🎉
  return (
    <div>
      <h3>{film.title}</h3>
      <p>{film.releaseDate}</p>
    </div>
  )
}
 
export default Film

useFragment() is not a React hook

useFragment() can be used without following React's rules of hooks. To avoid any issue with ESLint, we recommend changing its naming to getFragmentData() by setting the proper unmaskFunctionName value:

codegen.ts
import { CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx', '!src/gql/**/*'],
  generates: {
    './src/gql/': {
      preset: 'client',
      plugins: [],
      presetConfig: {
        fragmentMasking: { unmaskFunctionName: 'getFragmentData' }
      }
    }
  }
}
 
export default config

Getting a Fragment's type

To get a Fragment's type is achieved by importing the type that corresponds to your fragment, which is named based on the fragment name with a Fragment suffix:

src/Film.tsx
import { FilmItemFragment } from './gql'
 
const allFilmsWithVariablesQueryDocument = graphql(/* GraphQL */ `
  query allFilmsWithVariablesQuery($first: Int!) {
    allFilms(first: $first) {
      edges {
        node {
          ...FilmItem
        }
      }
    }
  }
`)
 
function myFilmHelper(film: FilmItemFragment) {
  // ...
}

Fragment Masking with nested Fragments

When dealing with nested Fragments, the useFragment() should also be used in a "nested way".

You can find a complete working example here: Nested Fragment example on GitHub (opens in a new tab).

Fragment Masking and testing

A React component that relies on Fragment Masking won't accept "plain object" as follows:

ProfileName.spec.ts
// ...
 
type ProfileNameProps = {
  profile: FragmentType<typeof ProfileName_PersonFragmentDoc>
}
 
const ProfileName = ({ profile }: ProfileNameProps) => {
  const { name } = useFragment(ProfileName_PersonFragmentDoc, profile)
  return (
    <div>
      <h1>Person Name: {name}</h1>
    </div>
  )
}
ProfileName.spec.ts
// ...
 
describe('<ProfileName />', () => {
  it('renders correctly', () => {
    const profile = { name: 'Adam' }
    render(
      <ProfileName
        profile={profile} // <-- this will throw TypeScript errors
      />
    )
 
    expect(screen.getByText('Person Name: Adam')).toBeInTheDocument()
  })
})

Since the component expect to receive "Masked data", you will need to import the makeFragmentData() helper to "build" some masked data, as follow:

ProfileName.spec.ts
// ...
import { makeFragmentData } from '../gql'
 
describe('<ProfileName />', () => {
  it('renders correctly', () => {
    const profile = { name: 'Adam' }
    render(<ProfileName profile={makeFragmentData(profile, ProfileName_PersonFragmentDoc)} />)
 
    expect(screen.getByText('Person Name: Adam')).toBeInTheDocument()
  })
})

How to disable Fragment Masking

client-preset's Fragment Masking can be disabled as follow:

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx', '!src/gql/**/*'],
  generates: {
    './src/gql/': {
      preset: 'client',
      plugins: [],
      presetConfig: {
        fragmentMasking: false
      }
    }
  }
}
 
export default config

Persisted documents

Persisted documents (often also referred to as persisted queries or persisted documents) is a technique for reducing client to server upstream traffic by sending a unique identifier instead of the full GraphQL document. It is also commonly used to reduce the size of the client bundle as well as to improve security by preventing the client from sending and executing arbitrary GraphQL operations.

Persisted documents can be enabled by setting the persistedDocuments option to true:

codegen.ts
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: 'schema.graphql',
  documents: ['src/**/*.tsx'],
  generates: {
    './src/gql/': {
      preset: 'client',
      plugins: [],
      presetConfig: {
        persistedDocuments: true
      }
    }
  }
}

By enabling this option GraphQL Code Generator will generate an additional file opersisted-documents.json within your artifacts location.

This file contains a mapping of the document's hash to the document's content.

persisted-documents.json
{
  "b2c3d4e5f6g7h8i9j0a1": "query Hello { hello }",
  "kae4fe7f6g7h8i9ej0ag": "mutation echo($msg: String!) { echo(message: $msg) }"
}

In addition the document hash will be added to the generated document node as a hash property.

app.ts
import { graphql } from './gql'
 
const HelloQuery = graphql(/* GraphQL */ `
  query Hello {
    hello
  }
`)
 
// logs "b2c3d4e5f6g7h8i9j0a1"
console.log(HelloQuery['__meta__']['hash'])

This hash can be used in the network layer of your GraphQL client to send the document hash instead of the document string.

Note that the server you sent the document hash to must be able to resolve the document hash to the document string.

Fetch example
import { graphql } from './gql'
 
const HelloQuery = graphql(/* GraphQL */ `
  query Hello {
    hello
  }
`)
 
const response = await fetch('http://yoga/graphql', {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    accept: 'application/json'
  },
  body: JSON.stringify({
    extensions: {
      persistedQuery: {
        version: 1,
        sha256Hash: HelloQuery['__meta__']['hash']
      }
    }
  })
})
 
console.log(response.status)
console.log(await response.json())

Reducing bundle size: Babel plugin

Large scale projects might want to enable code splitting or tree shaking on the client-preset generated files.

The client-preset comes with a Babel plugin that enables it.

To configure it, update (or create) your .babelrc.js as follow:

.babelrc.js
const { babelOptimizerPlugin } = require('@graphql-codegen/client-preset')
 
module.exports = {
  presets: ['react-app'],
  plugins: [[babelOptimizerPlugin, { artifactDirectory: './src/gql' }]]
}

Note that you will need to provide the artifactDirectory path that should be the same as the one configured in your codegen.ts

SWC plugin

A SWC plugin is planned for Next.js users, stay tuned by following the releases (opens in a new tab).