プログラミングマガジン

プログラミングを中心にIT技術をできるだけわかりやすくまとめます。

  • ホーム
  • React
  • 【Next.js】「Hasura」と「Apollo Client」の連携
 
 
     
  • サーバー言語  
    • Python
    • Ruby
    • PHP
    • SQL
  •  
  • インフラ  
       
    • AWS
    •  
    • 基本
    • Git
  • Web
       
    • Web開発
    • JavaScript
    • Vue.js
    • React
  •  
  • 設計  
       
    • 実装設計
    • DB設計
  • 問い合わせ
  

【Next.js】「Hasura」と「Apollo Client」の連携

10.08

  • miyabisan2
  • コメントを書く

この記事は3分で読めます

Next.jsではクライアントサイドとサーバーサイドの処理を扱うことができるのでApolloClientを使う場合は処理の切り分けが重要になってきます。

サーバーサイドで実行される箇所(SSG、ISR:getStaticProps、getStaticPaths)

Next.jsは毎回Apollo Clientのインスタンスを生成しなさいと謳っています。ちゃんとこの部分を意識しないとNext.jsのSSGやISRが変な挙動になってしまったりするので意識して実装する必要があります。

クライアントで実行される箇所

最初に1回だけApollo Clientのインスタンスを生成すれば良いですよと言っています。

Apollo Clientの実装例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import {
  ApolloClient,
  HttpLink,
  InMemoryCache,
  NormalizedCacheObject,
} from '@apollo/client'
import 'cross-fetch/polyfill'
 
let apolloClient: ApolloClient<NormalizedCacheObject> | undefined
const createApolloClient = () => {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: new HttpLink({
      uri: "HasuraのURL",
    }),
    cache: new InMemoryCache(),
  })
}
export const initializeApollo = (initialState = null) => {
  const _apolloClient = apolloClient ?? createApolloClient()
  // For SSG and SSR always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient
  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient
 
  return _apolloClient
}

ssrMode: typeof window === 'undefined'

windowはブラウザで実行しているかという意味になります。なので、ブラウザじゃない(サーバーサイド)の場合にtrueが入ります。クライアントサイドで実行する場合はfalseが入ります。

cache: new InMemoryCache(),

こちらはお決まりの文法でApollo Clientが自動でcacheに値を格納してくれます。

initializeApollo関数

windowオブジェクトを利用して、クライアントサイドは最初の1回だけインスタンスを生成して、サーバーサイドの場合は呼び出すたびにApolliClientインスタンスを生成するというような関数内容になっています。

呼び出し方

以下のように「ApolloProvider」を使ってNext.jsの_app.tsxなどに渡してあげます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import '../styles/globals.css'
import { AppProps } from 'next/app'
import { ApolloProvider } from '@apollo/client'
import { initializeApollo } from '../lib/apolloClient'
 
function MyApp({ Component, pageProps }: AppProps) {
  const client = initializeApollo()
  return (
    <ApolloProvider client={client}>
      <Component {...pageProps} />
    </ApolloProvider>
  )
}
 
export default MyAppこ

こうすることでプロジェクト内のあらゆるコンポーネントでApollo Clientが使えるようになります。

コンポーネント側からの呼び出し:クエリ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useQuery } from '@apollo/client'
import { GET_USERS } from '../queries/queries'
import { GetUsersQuery } from '../types/generated/graphql' // graphql-codegenで作られた型ファイル
 
const FetchMain = () => {
  const { data, error } = useQuery<GetUsersQuery>(GET_USERS, {
    //fetchPolicy: 'network-only',
    fetchPolicy: 'cache-and-network',
    //fetchPolicy: 'cache-first',
    //fetchPolicy: 'no-cache',
  })
 
  return (
    <Main />
  )
}

fetchPolicy

基本最初に読み込みに行く画面につけるオプションになります。

値 説明
cache-first fetchPolicyの指定を省略した場合のデフォルト値、キャッシュが存在する場合はキャッシュを見にいく動きになります。

注意点としては、仮にサーバーサイドで新しいデータが生成されたとしても最初に取得したデータだけを表示し続けることになります。データが頻繁に変わるようなアプリケーションでは望ましくないです。

network-only 毎回、キャッシュを使わずGraphQLのサーバーにアクセスするオプションです。さらに毎回cacheには結果を格納してくれます。

通信中は何もデータが表示されません。通信完了後に一気にデータが表示されます。

cache-and-network network-onlyとよく似ています。一点違いがあるとすれば通信中はcacheのデータを表示してくれます。通信完了後は最新のデータを表示してくれるようになっています。多くのアプリケーションではこちらを選んでおけば問題ないです。
no-cache これの場合、そもそもcacheにデータを格納しないので、別画面で@clientでcacheにアクセスしたとしても値を取得することができなくなります。毎回、サーバーにアクセスするので、通常のJSのaxiosでサーバーにアクセスする手法と似た手法になります。
キャッシュを読み込むかをクエリ側で制御する方法(@client)

すでにcacheがあることが前提の画面などで使う手法になります。

1
2
3
4
5
6
7
8
9
export const GET_USERS_LOCAL = gql`
  query GetUsers {
    users(order_by: { created_at: desc }) @client {
      id
      name
      created_at
    }
  }
`

@clientというものをつけます。これをつけたクエリはキャッシュにあればそれを読み込んでくれます。

この場合は、コンポーネント側のfetchPolicyを指定したとしても意味をなさなくなります。

data

以下のように__typenameの部分もやってきます。

Apollo Clientではこの__typename、idをキーにしてcacheに保存されたデータを探しにくいメカニズムになっています。

コンポーネント側からの呼び出し:登録系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useMutation } from '@apollo/client'
import {CREATE_USER } from '../queries/queries'
import { CreateUserMutation } from '../types/generated/graphql'
 
  const [insert_users_one] = useMutation<CreateUserMutation>(CREATE_USER, {
    update(cache, { data: { insert_users_one } }) {
      const cacheId = cache.identify(insert_users_one)
      cache.modify({
        fields: {
          users(existingUsers, { toReference }) {
            return [toReference(cacheId), ...existingUsers]
          },
        },
      })
    },
  })
 
    // 登録ボタン押下時の処理
  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
     await insert_users_one({variables: {name: editedUser.name }})
  }

insertはApolloが自動でcacheを更新してくれないので更新処理を別途updateという関数を使って記述する必要があります。

insert_users_one

Hasuraから返ってくるフィールドになります。

cache.identify

Apolloの機能です。新規で登録したデータのcacheのidを取得できます。

cache.modify({fields: {

Apolloのcacheを書き換えます。第一引数に書き換えたいフィールドを指定します。

toReference(cacheId)

Apolloの機能でIDに紐づいたデータをcacheから参照できます。既存のcacheの配列の先頭に今Hasuraに登録したデータを足しています。

コンポーネント側からの呼び出し:更新系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { useMutation } from '@apollo/client'
import {UPDATE_USER } from '../queries/queries'
import { UpdateUserMutation } from '../types/generated/graphql'
 
  const [update_users_by_pk] = useMutation<UpdateUserMutation>(UPDATE_USER)
 
  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
      try {
        await update_users_by_pk({
          variables: {
            id: editedUser.id,
            name: editedUser.name,
          },
        })
     }
}

updateはApolloが自動でcacheを更新してくれます。

コンポーネント側からの呼び出し:削除系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { useMutation } from '@apollo/client'
import {DELETE_USER } from '../queries/queries'
import { DeleteUserMutation } from '../types/generated/graphql'
 
  const [delete_users_by_pk] = useMutation<DeleteUserMutation>(DELETE_USER, {
    update(cache, { data: { delete_users_by_pk } }) {
      cache.modify({
        fields: {
          users(existingUsers, { readField }) {
            return existingUsers.filter(
              (user) => delete_users_by_pk.id !== readField('id', user)
            )
          },
        },
      })
    },
  })
 
  // 削除ボタン押下時の処理
  onClick={async () => {
     await delete_users_by_pk({variables: {id: user.id }})
   }}

deleteはApolloが自動でcacheを更新してくれないので更新処理を別途updateという関数を使って記述する必要があります。

readField

Apolloの機能で、cacheから任意のフィールドの値を読むことができます。

スポンサーリンク
  • 2022 10.08
  • miyabisan2
  • コメントを書く
  • GraphQL, Hasura, Next.js, React
  • Tweets Twitter
  • このエントリーをはてなブックマークに追加
  • LINEで送る

関連記事

  1. 2022 10.03

    【GraphQL】構成(「クエリ言語」、「スキーマ言語」)

  2. 2023 09.02

    【Next.js】サーバーコンポーネントについて

  3. 2020 12.22

    【React】JSX、Componentの基本

  4. 2021 02.27

    【React】TypeScriptで用意されている型一覧

  5. 2022 06.04

    【React】バージョン「18」について

  6. 2021 06.13

    【React】「Redux Tool Kit」のテスト

  • コメント ( 0 )
  • トラックバック ( 0 )
  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

返信をキャンセルする。

【React】GraphQL &Apollo Clie…

【Next.js】SSGでApollo Clientを…

RETURN TOP

著者プロフィール

エンジニア歴10年で過去に業務系、Webデザイン、インフラ系なども経験あります。現在はWeb系でフロントエンド開発中心です。

詳細なプロフィールはこちら

スポンサーリンク

カテゴリー

  • Android
  • AngularJS
  • API
  • AWS
  • C++
  • CSS
  • cursor
  • C言語
  • DDD
  • DevOps
  • Django
  • Docker
  • Figma
  • Git
  • GitLab
  • GraphQL
  • gRPC
  • Hasura
  • Java
  • JavaScript
  • Kubernetes
  • Laravel
  • linux
  • MySQL
  • Next.js
  • nginx
  • Node.js
  • NoSQL
  • Nuxt.js
  • Oracle
  • PHP
  • Python
  • React
  • Redux
  • Rspec
  • Ruby
  • Ruby on Rails
  • Sass
  • Spring Framework
  • SQL
  • TypeScript
  • Unity
  • Vue.js
  • Webサービス開発
  • Webデザイン
  • Web技術
  • インフラ
  • オブジェクト指向
  • システム開発
  • セキュリティ
  • その他
  • データベース
  • デザインパターン
  • テスト
  • ネットワーク
  • プログラミング全般
  • マイクロサービス
  • マイクロソフト系技術
  • マルチメディア
  • リファクタリング
  • 副業
  • 未分類
  • 業務知識
  • 生成AI
  • 設計
  • 関数型言語
RETURN TOP

Copyright ©  プログラミングマガジン | プライバシーポリシー