Client preset
Package name | Weekly Downloads | Version | License | Updated |
---|---|---|---|---|
@graphql-codegen/client-preset | Dec 27th, 2022 |
Installation
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
(since3.2.0
, not when using React Components (<Query>
))@urql/core
(since1.15.0
)@urql/preact
(since1.4.0
)urql
(since1.11.0
)graphql-request
(since5.0.0
)react-query
(withgraphql-request@5.x
)swr
(withgraphql-request@5.x
)
-
Vue
@vue/apollo-composable
(since4.0.0-alpha.13
)villus
(since1.0.0-beta.8
)@urql/vue
(since1.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
: Ifscalars
are found in the schema that are not defined in scalars an error will be thrown during codegen.namingConvention
: Available case functions inchange-case-all
arecamelCase
,capitalCase
,constantCase
,dotCase
,headerCase
,noCase
,paramCase
,pascalCase
,pathCase
,sentenceCase
,snakeCase
,lowerCase
,localeLowerCase
,lowerCaseFirst
,spongeCase
,titleCase
,upperCase
,localeUpperCase
andupperCaseFirst
useTypeImports
: Will useimport type {}
rather thanimport {}
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 uniontype
instead of anenum
. 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 concernsarrayInputCoercion
: The GraphQL spec 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.
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:
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's article: Unleash the power of Fragments with GraphQL Codegen
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).
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):
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:
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:
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.
Fragment Masking and testing
A React component that relies on Fragment Masking won't accept "plain object" as follows:
// ...
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>
)
}
// ...
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:
// ...
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:
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: false
}
}
}
}
export default config
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:
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.