Apollo Client + React入門:TypeScriptでGraphQL CRUDを実装する

スポンサーリンク

Apollo Client + React入門:TypeScriptでGraphQL CRUDを実装する

はじめに

この記事は「GraphQL入門:TypeScriptでスキーマ・Query・Mutationを実装する」の続きです。Apollo Serverで立ち上げたGraphQLサーバーに対し、React + Apollo ClientでCRUD操作を実装します。


セットアップ

Reactプロジェクトを作成する

npm create vite@latest graphql-client -- --template react-ts
cd graphql-client
npm install @apollo/client graphql
npm run dev

Apollo Clientを設定する

// src/apollo.ts
import { ApolloClient, InMemoryCache } from '@apollo/client'

export const client = new ApolloClient({
  uri: 'http://localhost:4000',
  cache: new InMemoryCache(),
})
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { ApolloProvider } from '@apollo/client'
import { client } from './apollo'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
  </StrictMode>
)

ApolloProvider でアプリ全体をラップすることで、どのコンポーネントからでも useQuery / useMutation が使えます。


型定義

// src/types.ts
export type User = {
  id: string
  name: string
  email: string
}

Query(Read:データ取得)

useQuery でQueryを実行します。コンポーネントのマウント時に自動で実行されます。

// src/components/UserList.tsx
import { gql, useQuery } from '@apollo/client'
import type { User } from '../types'

const GET_USERS = gql`
  query GetUsers {
    users {
      id
      name
      email
    }
  }
`

type GetUsersData = { users: User[] }

export function UserList() {
  const { loading, error, data } = useQuery<GetUsersData>(GET_USERS)

  if (loading) return <p>読み込み中...</p>
  if (error) return <p>エラー: {error.message}</p>

  return (
    <ul>
      {data?.users.map(user => (
        <li key={user.id}>
          {user.name}{user.email}</li>
      ))}
    </ul>
  )
}

useQuery の返り値:

プロパティ 説明
loading 取得中かどうか
error エラー情報
data 取得したデータ
refetch 再取得を手動でトリガー

Mutation(Create:作成)

// src/components/CreateUser.tsx
import { useState } from 'react'
import { gql, useMutation } from '@apollo/client'

const GET_USERS = gql`
  query GetUsers {
    users { id name email }
  }
`

const CREATE_USER = gql`
  mutation CreateUser($name: String!, $email: String!) {
    createUser(name: $name, email: $email) {
      id
      name
      email
    }
  }
`

export function CreateUser() {
  const [name, setName] = useState('')
  const [email, setEmail] = useState('')

  const [createUser, { loading }] = useMutation(CREATE_USER, {
    refetchQueries: [{ query: GET_USERS }],
  })

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    await createUser({ variables: { name, email } })
    setName('')
    setEmail('')
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={e => setName(e.target.value)} placeholder="名前" />
      <input value={email} onChange={e => setEmail(e.target.value)} placeholder="メール" />
      <button type="submit" disabled={loading}>作成</button>
    </form>
  )
}

refetchQueriesGET_USERS を指定することで、作成後に一覧を自動再取得します。


Mutation(Update:更新)

// src/components/UpdateUser.tsx
import { useState } from 'react'
import { gql, useMutation } from '@apollo/client'

const UPDATE_USER = gql`
  mutation UpdateUser($id: ID!, $name: String, $email: String) {
    updateUser(id: $id, name: $name, email: $email) {
      id
      name
      email
    }
  }
`

export function UpdateUser({ id }: { id: string }) {
  const [name, setName] = useState('')

  const [updateUser] = useMutation(UPDATE_USER)

  return (
    <span>
      <input value={name} onChange={e => setName(e.target.value)} placeholder="新しい名前" />
      <button onClick={() => updateUser({ variables: { id, name } })}>更新</button>
    </span>
  )
}

Apollo InMemoryCacheは id フィールドをキーにキャッシュを管理します。更新系MutationでサーバーがIDつきのオブジェクトを返すと、キャッシュが自動で書き換わるため refetchQueries は不要です。


Mutation(Delete:削除)

// src/components/DeleteUser.tsx
import { gql, useMutation } from '@apollo/client'

const GET_USERS = gql`
  query GetUsers {
    users { id name email }
  }
`

const DELETE_USER = gql`
  mutation DeleteUser($id: ID!) {
    deleteUser(id: $id)
  }
`

export function DeleteUser({ id }: { id: string }) {
  const [deleteUser] = useMutation(DELETE_USER, {
    refetchQueries: [{ query: GET_USERS }],
  })

  return (
    <button onClick={() => deleteUser({ variables: { id } })}>削除</button>
  )
}

削除はキャッシュの自動更新が効かないため、refetchQueries で一覧を再取得します。


まとめ

操作 Hook キャッシュ更新
取得(Read) useQuery マウント時に自動実行
作成(Create) useMutation refetchQueries で再取得
更新(Update) useMutation id 一致でキャッシュ自動更新
削除(Delete) useMutation refetchQueries で再取得
  • ApolloProvider でアプリ全体をラップするとどのコンポーネントからでもHookが使える
  • useMutation の第1戻り値が実行関数、第2戻り値が { loading, error, data }
  • Apollo InMemoryCacheが id をキーに管理するため、更新系はRefetch不要なことが多い
  • 削除はキャッシュから自動で消えないので refetchQueriesupdate で対応する

バックエンドの実装は「GraphQL入門:TypeScriptでスキーマ・Query・Mutationを実装する」を参照してください。