website/src/pages/plugins/typescript/typescript-react-query.mdx
import { Callout } from '@theguild/components' import { PluginApiDocs, PluginHeader } from '@/components/plugin' import { pluginGetStaticProps } from '@/lib/plugin-get-static-props' export const getStaticProps = pluginGetStaticProps(__filename, { hasOperationsNote: true })
<Callout type="info">We now recommend using the client-preset package for a better developer experience and smaller impact on bundle size.
Get started on our "React/Vue" guide.
</Callout> <PluginHeader /> <PluginApiDocs />fetchBy default, this plugin will generate a fetcher based on the environment global fetch definition.
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'MY_SCHEMA_PATH',
documents: './src/**/*.graphql',
generates: {
'./generates.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-query'],
config: {
fetcher: 'fetch'
}
}
}
}
export default config
To use the generated hooks, import it, and then specify the endpoint and optionally fetchParams:
import { useMyQuery } from './generated'
export const MyComponent = () => {
const { status, data, error, isFetching } = useMyQuery({
endpoint: 'http://localhost:3000/graphql',
fetchParams: {
headers: {
'My-Header': 'XYZ'
}
}
})
}
fetch with Codegen configurationIf you wish to avoid specifying endpoint and fetchParams on each hook usage, you can specify those in the codegen.yml file:
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'MY_SCHEMA_PATH',
documents: './src/**/*.graphql',
generates: {
'./generates.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-query'],
config: {
fetcher: {
endpoint: 'http://localhost:3000/graphql',
fetchParams: {
headers: {
'My-Header': 'SomeValue'
}
}
}
}
}
}
}
export default config
And if you wish to have more control over the value, or even provide it in runtime, you can use environment variables:
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'MY_SCHEMA_PATH',
documents: './src/**/*.graphql',
generates: {
'./generates.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-query'],
config: {
fetcher: {
endpoint: 'process.env.ENDPOINT'
}
}
}
}
}
export default config
You can even use a custom variable from your code, and add custom imports with add plugin:
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'MY_SCHEMA_PATH',
documents: './src/**/*.graphql',
generates: {
'./generates.ts': {
plugins: [
{
add: {
content: "import { endpointUrl, fetchParams } from './my-config';"
}
},
'typescript',
'typescript-operations',
'typescript-react-query'
],
config: {
fetcher: {
endpoint: 'endpointUrl',
fetchParams: 'fetchParams'
}
}
}
}
}
export default config
The generated hooks doesn't require you to specify anything, you can just use it as-is:
import { useMyQuery } from './generated'
export const MyComponent = () => {
const { status, data, error, isFetching } = useMyQuery({})
}
graphql-requestIf you are using graphql-request, you can set fetcher to graphql-request, and then the generated React Hook will expect you to pass the GraphQLClient instance (created by graphql-request library).
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'MY_SCHEMA_PATH',
documents: './src/**/*.graphql',
generates: {
'./generates.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-query'],
config: {
fetcher: 'graphql-request'
}
}
}
}
export default config
And the, while using, provide your client instance:
import { useMyQuery } from './generated'
import { client } from './my-graphql-request-client'
export const MyComponent = () => {
const { status, data, error, isFetching } = useMyQuery(client, {})
}
If you wish to create a custom fetcher, you can provide your own function as a Mapper string (file#identifier). Codegen will take care of importing it and use it as a fetcher.
import type { CodegenConfig } from '@graphql-codegen/cli'
const config: CodegenConfig = {
schema: 'MY_SCHEMA_PATH',
documents: './src/**/*.graphql',
generates: {
'./generates.ts': {
plugins: ['typescript', 'typescript-operations', 'typescript-react-query'],
config: {
fetcher: {
func: './my-file#myFetcher',
isReactHook: false // optional, defaults to false, controls the function's signature. Read below
}
}
}
}
}
export default config
As a shortcut, the fetcher property may also directly contain the function as a mapper string:
# …
config:
fetcher: './my-file#myFetcher' # isReactHook is false here (the default version)
Codegen will use myFetcher, and you can just use the hook directly:
import { useMyQuery } from './generated'
export const MyComponent = () => {
const { status, data, error, isFetching } = useMyQuery({})
}
Depending on the isReactHook property, your myFetcher should be in the following signature:
isReactHook: false
type MyFetcher<TData, TVariables> = (operation: string, variables?: TVariables, options?: RequestInit['headers']): (() => Promise<TData>)
isReactHook: true
type MyFetcher<TData, TVariables> = (operation: string, options?: RequestInit['headers']): ((variables?: TVariables) => Promise<TData>)
isReactHook: false)export const fetchData = <TData, TVariables>(
query: string,
variables?: TVariables,
options?: RequestInit['headers']
): (() => Promise<TData>) => {
return async () => {
const res = await fetch('https://api.url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...options
},
body: JSON.stringify({
query,
variables
})
})
const json = await res.json()
if (json.errors) {
const { message } = json.errors[0] || {}
throw new Error(message || 'Error…')
}
return json.data
}
}
isReactHook: true)export const useFetchData = <TData, TVariables>(
query: string,
options?: RequestInit['headers']
): ((variables?: TVariables) => Promise<TData>) => {
// it is safe to call React Hooks here.
const { url, headers } = React.useContext(FetchParamsContext)
return async (variables?: TVariables) => {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...headers,
...options
},
body: JSON.stringify({
query,
variables
})
})
const json = await res.json()
if (json.errors) {
const { message } = json.errors[0] || {}
throw new Error(message || 'Error…')
}
return json.data
}
}
If you wish to use infinite query for pagination or infinite scroll you can with the addInfiniteQuery config setting. This will however setup an infinite query for every request whether in reality it can do it or not.
To use this you need to return an object of new queries, and it blends them in to the query.
addInfiniteQuery: true)with the following query:
query AnimalsQuery($catsRange: Int, $catsStarting: Int, $dogsRange: Int, $dogsStarting: Int) {
cats(range: $catsRange, starting: $catsStarting) {
# …
}
dogs(range: $dogsRange, starting: $dogsStarting) {
# …
}
}
import { useInfiniteMyQuery } from './generated'
export const MyComponent = () => {
const { status, data, error, isFetching } = useInfiniteAnimalsQuery(
{
catsRange: 5,
catsStarting: 0,
dogsRange: 10,
dogsStarting: 0
},
{
getNextPageParam(lastPage, allPages) {
const totalLocal = (allPages.length ?? 0) * (queryParams.limit ?? 1)
const totalDogs = lastPage.dogs.items?.length ?? 0
if (totalLocal < totalDogs) {
return {
catsStarting: totalLocal * 5,
dogsStarting: totalLocal * 10
}
}
}
}
)
}