# GraphQL Client
## SHARE
`graphql-request > @apollo/client > react-relay = urql`

https://npmtrends.com/@apollo/client-vs-graphql-request-vs-react-relay-vs-urql
## SIZE(MINIFIED + GZIPPED)
`react-relay > @apollo/client > urql > graphql-request`
* graphql-request: 7.6kB
* https://bundlephobia.com/package/graphql-request@4.3.0
* @apollo/client: 40kB
* https://bundlephobia.com/package/@apollo/client@3.6.9
* react-relay: 55.6kB
* https://bundlephobia.com/package/react-relay@14.1.0
* urql: 8.5kB
* https://bundlephobia.com/package/urql@2.2.3
## Functions
* graphql-request
* 最もシンプルで軽量なGraphQL クライアント
* https://www.npmjs.com/package/graphql-request
* @apollo/client
* Apollo Clientは JavaScript 用の包括的な状態管理ライブラリであり、GraphQL を使用してローカル データとリモート データの両方を管理できます。これを使用して、UI を自動的に更新しながら、アプリケーション データをフェッチ、キャッシュ、および変更します。
* https://www.apollographql.com/docs/react/
* Suspense対応が遅い(今もされてない?)
* react-relay
* Relay は、React アプリケーションで GraphQL データを取得および管理するための JavaScript フレームワークであり、保守性、タイプ セーフ、およびランタイム パフォーマンスを重視しています。
* Relay は、宣言型データ フェッチと静的ビルド ステップを組み合わせることでこれを実現します。宣言型のデータ取得では、コンポーネントが必要なデータを宣言し、Relay がデータを効率的に取得する方法を見つけ出します。静的ビルド ステップ中に、Relay はクエリを検証して最適化し、アーティファクトを事前に計算して実行時のパフォーマンスを向上させます。
* https://relay.dev/docs/
* Reactと作ってる会社一緒なので、Suspense対応とかが早い
* urql
* urqlは高度にカスタマイズ可能で汎用性の高い GraphQL クライアントであり、成長に合わせて正規化されたキャッシュなどの機能を追加できます。これは、GraphQL の初心者にとって使いやすく、拡張可能で、動的な単一アプリ アプリケーションと高度にカスタマイズされた GraphQL インフラストラクチャをサポートするように構築されています。
* https://formidable.com/open-source/urql/docs/
* Suspense対応済み
## in short
* graphql-request
* シェアが高く、最もシンプルなGraphQL Client
* @apollo/client
* シェアが高く、状態管理機能をメインとしたGraphQL Client
* react-relay
* 最も高機能なGraphQL Client
* urql
* キャッシュ機構を備えた拡張性のあるGraphQL Client
* JS及びReactのコンサル企業であるFormidableによって開発された
## How to use
### graphql-request
https://github.com/prisma-labs/graphql-request
#### codegen
```yaml
generated/graphql-request.ts:
plugins:
- typescript
- typescript-operations
- typescript-graphql-request
```
https://www.graphql-code-generator.com/plugins/typescript/typescript-graphql-request
#### client
```typescript
import { GraphQLClient } from 'graphql-request'
import { getSdk } from '../schema/generated/graphql-request'
const client = new GraphQLClient("http://localhost:9002/graphql")
const sdk = getSdk(client)
sdk.books()
```
#### キャッシュ機構を使いたいなら、SRWとセットで使う
##### コードサンプル
`pages/graphql-request.tsx`
```typescript
import type { NextPage } from 'next'
import styles from '@styles/Home.module.css'
import dynamic from 'next/dynamic'
const BooksFetch = dynamic(
() => {
return import('@atoms/graphql-request-books')
},
{
ssr: false,
}
)
const Home: NextPage = () => {
return (
<div className={styles.container}>
<main className={styles.main}>
<BooksFetch />
</main>
</div>
)
}
export default Home
```
`@atoms/graphql-request-books/index.tsx`
```typescript
import { memo, Suspense } from 'react'
import useBooks from './hooks'
function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const SometimesSuspend = () => {
if (Math.random() < 0.5) {
console.log('sleep..')
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw sleep(1000)
}
return <p>Hello, world!</p>
}
const BooksComponent = () => {
const { data } = useBooks()
return (
<div>
<SometimesSuspend />
{data?.books
?.map((v) => {
return `{${v?.author}, ${v?.title}}`
})
.toString()}
</div>
)
}
// eslint-disable-next-line react/display-name
const BooksFetch = memo(() => {
return (
<Suspense fallback={<div>loading...</div>}>
<BooksComponent />
</Suspense>
)
})
export default BooksFetch
```
`@atoms/graphql-request-books/hooks.tsx`
```typescript
import useSWR from 'swr'
import { GraphQLClient } from 'graphql-request'
import { getSdk, BooksQuery, BooksDocument } from '../../../schema/generated/typescript/graphql-request'
const useBooks = () => {
const client = new GraphQLClient('http://localhost:9002/graphql')
const sdk = getSdk(client)
const { data, error } = useSWR<BooksQuery>([BooksDocument], sdk.books, {
suspense: true,
revalidateOnFocus: false,
revalidateIfStale: false,
revalidateOnReconnect: false,
})
return { data, error }
}
export default useBooks
```
##### Suspenseの使い方
https://zenn.dev/uhyo/books/react-concurrent-handson
##### SWRの使い方
シンプルな `graphql-client` とセットで使い、キャッシュまわりはSWRで管理する
SWRとは?
https://zenn.dev/yukikoma/articles/17adad7fedd5af
SWRのオプション
https://swr.vercel.app/docs/options
##### はまりどころ
Next.jsでSuspense利用時に、Hydration failedで表示がエラーになってしまう
対処法
https://qiita.com/horihorikei/items/4c467009a596d24949b2
### urql
https://github.com/FormidableLabs/urql
#### codegen
```yaml
generated/urql.tsx:
plugins:
- typescript
- typescript-operations
- typescript-urql
```
https://www.graphql-code-generator.com/plugins/typescript/typescript-urql
#### client
```typescript
import { withUrqlClient } from 'next-urql'
import { useBooksQuery } from '../schema/generated/urql'
const Home = () => {
const [res] = useBooksQuery()
console.log(res)
...
}
export default withUrqlClient(() => {
return {
url: 'http://localhost:9002/graphql',
}
})(Home)
```
公式
https://formidable.com/open-source/urql/docs/advanced/server-side-rendering/#nextjs
サンプル
https://github.com/FormidableLabs/urql/tree/main/examples/with-next
オススメしている記事
https://zenn.dev/adwd/articles/f4c5c5120467bb
#### キャッシュ機構
```
すべてのquery と variable の組み合わせはハッシュとして保持され、
レスポンスと共にキャッシュされるため、
再度同じリクエうとが投げられるとリクエストは投げられずキャッシュされます。
document caching では、
キャッシュに残ってる query に対しての mutation を実行した時、
値が更新されると仮定して現在の query のキャッシュを無効化します。
```
この記事で掘り下げている
https://zenn.dev/takurinton/articles/ee14cdd8a1630c
https://dev.takurinton.com/tech/graphql/graphcache.html#document-caching-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6
#### Suspenseの使い方
`pages/urql.tsx`
```typescript
import type { NextPage } from 'next'
import { withUrqlClient } from 'next-urql'
import styles from '@styles/Home.module.css'
import dynamic from 'next/dynamic'
const BooksFetch = dynamic(
() => {
return import('@/components/atoms/urql-books')
},
{
ssr: false,
}
)
const Home: NextPage = () => {
return (
<div className={styles.container}>
<main className={styles.main}>
<BooksFetch />
</main>
</div>
)
}
export default withUrqlClient(() => {
return {
url: 'http://localhost:9002/graphql',
suspense: true,
}
})(Home)
```
`@/components/atoms/urql-books.tsx`
```typescript
import { Suspense } from 'react'
import { useBooksQuery } from '../../../schema/generated/urql'
function sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
const SometimesSuspend = () => {
if (Math.random() < 0.5) {
console.log('sleep..')
// eslint-disable-next-line @typescript-eslint/no-throw-literal
throw sleep(1000)
}
return <p>Hello, world!</p>
}
const BooksComponent = () => {
const [res] = useBooksQuery()
return (
<div>
<SometimesSuspend />
{res.data?.books
?.map((v) => {
return `{${v?.author}, ${v?.title}}`
})
.toString()}
</div>
)
}
// eslint-disable-next-line react/display-name
const BooksFetch = () => {
return (
<Suspense fallback={<div>loading...</div>}>
<BooksComponent />
</Suspense>
)
}
export default BooksFetch
```
### Apollo Client
https://www.apollographql.com/docs/react/
#### codegen
```yaml
generated/typescript/react-apollo.ts:
plugins:
- typescript
- typescript-operations
- typescript-react-apollo
```
https://www.graphql-code-generator.com/plugins/typescript/typescript-react-apollo
#### キャッシュ機構
クエリの結果をインメモリのキャッシュに自動で保存してくれる
https://qiita.com/tokio_dev/items/945670f580c5cf507d97
https://zenn.dev/kazu777/articles/b64935ea7d6fee
##### InMemoryCache について
**前提**
↓ を実行することで、クエリが実行され `client.cache` にデータが溜まっていく
```
const query = gql`
query books {
books {
title
}
}`
client.query({ query })
```
↓ `client.cache.data.data`の中身(データにIDが含まれていない場合)
```
{
"ROOT_QUERY": {
"__typename": "Query",
"books": [
{
"__typename": "Book",
"title": "Harry Potter and the Chamber of Secrets"
},
{
"__typename": "Book",
"title": "Jurassic Park : ENV[undefined]"
}
]
}
}
```
データにIDが含まれている場合はこうなる
```
cache.writeQuery({
query,
data: { books: [{ id: '1111', title: 'john', __typename: 'Book' }] }
})
```
```
{
"Book:1111": {
"__typename": "Book",
"title": "john",
"id": "1111"
},
"ROOT_QUERY": {
"__typename": "Query",
"books": [
{
"__ref": "Book:1111"
}
]
}
}
```
→ 勝手に` __typename:ID` というフォーマットがキーになる
##### Apollo Clientのキャッシュ操作
5種のメソッドが生えている
* readQuery
↓Queryを叩いた後、readQueryを実行することで値が取れる
```
client.readQuery({ query })
```
```
{
"books": [
{
"__typename": "Book",
"title": "john"
}
]
}
```
Queryから得られる値は固定なので、booksのキャッシュデータが返ってる
IDとか引数指定されてたらどうなるんだ?
```
// ID指定
const query = gql`
query books($id: ID!) {
books(id: $id) {
title
}
}`
// ID指定で実行
cache.writeQuery({
query,
variables: {id: 3},
data: { books: [{ id: '1111', title: 'john', __typename: 'Book' }] }
})
// ID指定でキャッシュ取得(ID指定しないとnull
client.readQuery({
query,
variables: {id: 3},
});
```
↓ キャッシュの中身
```
{
"Book:1111": {
"__typename": "Book",
"title": "john",
"id": "1111"
},
"ROOT_QUERY": {
"__typename": "Query",
"books({\"id\":3})": [
{
"__ref": "Book:1111"
}
]
}
}
```
* writeQuery
キャッシュに書き込むことが出来る
* updateQuery
キャッシュを更新することが出来る(試してないので、たぶん
* readFragment
fragmentとキャッシュのキーを指定することで、部分的に値を取れる
```
const fragment = gql`
fragment hoge on Book {
title
}
client.readFragment({
id: 'Book:1111',
fragment: fragment,
})
```
```
{
"__typename": "Book",
"title": "john"
}
```
* writeFragment
部分的に書き込める感じだと思う(試してないので、たぶん
参考
https://www.apollographql.com/docs/react/api/cache/InMemoryCache
https://tech.jxpress.net/entry/understand-apollo-cache
https://www.apollographql.com/docs/react/caching/overview/
ツール
https://chrome.google.com/webstore/detail/apollo-client-devtools/jdkknkkbebbapilgoeccciglkfbmbnfm
### relay
https://relay.dev/
ここまでリッチなものは今いま必要ないので割愛
# 状態管理
## Recoil
Facebook製のReact用状態管理ライブラリ
Context APIのデメリットとReduxの使いづらさを解消するために開発された


https://ics.media/entry/210224/
# GraphQL Client と コンポーネントの接続
## Fragment Colocation
コンポーネントとセットで、
そのコンポーネントが必要なデータを Fragment で宣言する、
React の宣言的 UI に合わせた宣言的データ取得(declarative data-fetching)という考え方
(例)
```typescript
import type { NextPage } from 'next'
import { GraphQLClient } from 'graphql-request'
import SampleBook from '@atoms/SampleBook'
import { getSdk, BooksQuery } from '@graphql-client'
type Props = {
book: BooksQuery
}
const SsrBook: NextPage<Props> = ({ book }) => {
return (
<div>
<main>
<SampleBook fragment={book.book1} />
<SampleBook fragment={book.book2} />
<SampleBook fragment={book.book3} />
</main>
</div>
)
}
SsrBook.getInitialProps = async (): Promise<Props> => {
const client = new GraphQLClient('http://localhost:9002/graphql')
const sdk = getSdk(client)
const book = await sdk.books()
return { book }
}
export default SsrBook
```
https://zenn.dev/izumin/scraps/46aba5181afe82
https://tech.uzabase.com/entry/2022/06/30/164016
## 使い方
https://maasaablog.com/development/react/3226/
https://nulab.com/ja/blog/nulab/recoil-example/
# reference
https://user-first.ikyu.co.jp/entry/2022/07/01/121325
https://nulab.com/ja/blog/nulab/graphql-apollo-relay-urql/
https://zenn.dev/seya/scraps/9d64f2e9cae500