graphql-codegen-plugin-typescript-swr

A GraphQL code generator plug-in that automatically generates utility functions for SWR.

Usage no npm install needed!

<script type="module">
  import graphqlCodegenPluginTypescriptSwr from 'https://cdn.skypack.dev/graphql-codegen-plugin-typescript-swr';
</script>

README

graphql-codegen-plugin-typescript-swr

A GraphQL code generator plug-in that automatically generates utility functions for SWR.

Table of Contents

API Reference

excludeQueries

type: string | string[] default: ""

Exclude queries that are matched by micromatch (case-sensitive).

useSWRInfinite

type: string | string[] default: ""

Add useSWRInfinite() wrapper for the queries that are matched by micromatch (case-sensitive).

autogenSWRKey

type: boolean default: false

Generate key to use useSWR() automatically.
But, ​the cache may not work unless you separate the variables object into an external file and use it, or use a primitive type for the value of each field.

Config Example

generates:
  path/to/graphql.ts:
    schema: 'schemas/github.graphql'
    documents: 'src/services/github/**/*.graphql'
    plugins:
      - typescript
      - typescript-operations
      # Put `plugin-typescript-swr` below `typescript-graphql-request`
      - typescript-graphql-request
      - plugin-typescript-swr
config:
  rawRequest: false
  excludeQueries:
    - foo*
    - bar
  useSWRInfinite:
    - hoge
    - bar{1,3}
  autogenSWRKey: true

Usage Examples

For the given input:

query continents {
  continents {
    name
    countries {
      ...CountryFields
    }
  }
}

fragment CountryFields on Country {
  name
  currency
}

It generates SDK you can import and wrap your GraphQLClient instance, and get fully-typed SDK based on your operations:

import { GraphQLClient } from 'graphql-request'
import { getSdkWithHooks } from './sdk'

function Continents() {
  const client = new GraphQLClient('https://countries.trevorblades.com/')
  const sdk = getSdkWithHooks(client)

  const { data, error } = sdk.useContinents('Continents')

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return (
    <ul>
      {data.continents.map((continent) => (
        <li>{continent.name}</li>
      ))}
    </ul>
  )
}

Pagination

codegen.yaml

config:
  useSWRInfinite:
    - MyQuery

Functional Component

const { data, size, setSize } = sdk.useMyQueryInfinite(
  'id_for_caching',
  (pageIndex, previousPageData) => {
    if (previousPageData && !previousPageData.search.pageInfo.hasNextPage)
      return null
    return [
      'after',
      previousPageData ? previousPageData.search.pageInfo.endCursor : null,
    ]
  },
  variables, // GraphQL Query Variables
  config // Configuration of useSWRInfinite
)

Authorization

import { GraphQLClient } from 'graphql-request'
import { getSdkWithHooks } from './sdk'
import { getJwt } from './jwt'

const getAuthorizedSdk = () => {
  const headers: Record<string, string> = { 'Content-Type': 'application/json' }
  const jwt = getJwt()
  if (jwt) {
    headers.Authorization = `Bearer ${jwt}`
  }
  return getSdkWithHooks(
    new GraphQLClient(`${process.env.NEXT_PUBLIC_API_URL}`, {
      headers,
    })
  )
}

export default getAuthorizedSdk

Next.js

// pages/posts/[slug].tsx
import { GetStaticProps, NextPage } from 'next'
import ErrorPage from 'next/error'
import { useRouter } from 'next/router'
import Article from '../components/Article'
import sdk from '../sdk'
import { GetArticleQuery } from '../graphql'

type StaticParams = { slug: string }
type StaticProps = StaticParams & {
  initialData: {
    articleBySlug: NonNullable<GetArticleQuery['articleBySlug']>
  }
}
type ArticlePageProps = StaticProps & { preview?: boolean }

export const getStaticProps: GetStaticProps<StaticProps, StaticParams> = async ({
  params,
  preview = false,
  previewData
}) => {
  if (!params) {
    throw new Error('Parameter is invalid!')
  }

  const { articleBySlug: article } = await sdk().GetArticle({
    slug: params.slug,
  })

  if (!article) {
    throw new Error('Article is not found!')
  }

  const props: ArticlePageProps = {
    slug: params.slug,
    preview,
    initialData: {
      articleBySlug: article
    }
  }

  return {
    props: preview
      ? {
          ...props,
          ...previewData,
        }
      : props,
  }
})

export const ArticlePage: NextPage<ArticlePageProps> = ({ slug, initialData, preview }) => {
  const router = useRouter()
  const { data: { article }, mutate: mutateArticle } = sdk().useGetArticle(
    `GetArticle/${slug}`, { slug }, { initialData }
  )

  if (!router.isFallback && !article) {
    return <ErrorPage statusCode={404} />
  }

  // because of typescript problem
  if (!article) {
    throw new Error('Article is not found!')
  }

  return (
    <Layout preview={preview}>
      <>
        <Head>
          <title>{article.title}</title>
        </Head>
        <Article article={article} />
      </>
    </Layout>
  )
}