React（Next.js/GraphQL）からHasuraを使ったCRUD操作

## はじめに

React（Next.js/GraphQL）からHasuraを使ったCRUD操作を何回かに分けて見ていきます。

今回は、ざっとCRUD全体の処理を作成して、実機で動くところまで見ていきます。

個々の処理やその説明は何回かに分けて説明します。

## 最終的なゴール

以下のような構成のアプリを作ることです。

![スクリーンショット 2021-10-18 17.27.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/e8a9a2e1-2723-4fec-77ef-be3b9eefdd81.png)

### 目的

- 仕事で使っている技術のキャッチアップと復習
- 使う可能性がある技術の理解度向上

## 前回のおさらい

apolloの4つの fetchPolicy を紹介しました

詳細👇

https://qiita.com/ryosuketter/items/398dbfddb9aae5eca5a9

## ファイルの用意

以下のファイルを作成します。

```
pages/hasura-crud.tsx
```

```
components/UserItem.tsx
```

## 必要なモジュールのimport

```tsx:pages/hasura-crud.tsx
import { VFC, useState, FormEvent } from 'react'
import { useQuery, useMutation } from '@apollo/client'
import {
  GET_USERS,
  CREATE_USER,
  DELETE_USER,
  UPDATE_USER,
} from '../queries/queries'
import {
  GetUsersQuery,
  CreateUserMutation,
  DeleteUserMutation,
  UpdateUserMutation,
} from '../types/generated/graphql'
import { Layout } from '../components/Layout'
```

`CRUD`の`CUD`は`useMutation`が必要なのでimportしています。

クエリ

```tsx
import {
  GetUsersQuery,
  CreateUserMutation,
  DeleteUserMutation,
  UpdateUserMutation,
} from '../types/generated/graphql'
```

も、使用するのでimportします。

実態は自動作成された`types/generated/graphql.tsx`にあります。

<details>
<summary>pages/hasura-crud.tsx</summary><div>


```tsx:pages/hasura-main.tsx
import { VFC, useState, FormEvent } from 'react'
import { useQuery, useMutation } from '@apollo/client'
import {
  GET_USERS,
  CREATE_USER,
  DELETE_USER,
  UPDATE_USER,
} from '../queries/queries'
import {
  GetUsersQuery,
  CreateUserMutation,
  DeleteUserMutation,
  UpdateUserMutation,
} from '../types/generated/graphql'
import { Layout } from '../components/Layout'
import { UserItem } from '../components/UserItem'

const HasuraCRUD: VFC = () => {
  const [editedUser, setEditedUser] = useState({ id: '', name: '' })
  const { data, error } = useQuery<GetUsersQuery>(GET_USERS, {
    fetchPolicy: 'cache-and-network',
  })
  const [update_users_by_pk] = useMutation<UpdateUserMutation>(UPDATE_USER)
  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 [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)
            )
          },
        },
      })
    },
  })
  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (editedUser.id) {
      try {
        await update_users_by_pk({
          variables: {
            id: editedUser.id,
            name: editedUser.name,
          },
        })
      } catch (err) {
        alert(err.message)
      }
      setEditedUser({ id: '', name: '' })
    } else {
      try {
        await insert_users_one({
          variables: {
            name: editedUser.name,
          },
        })
      } catch (err) {
        alert(err.message)
      }
      setEditedUser({ id: '', name: '' })
    }
  }
  if (error) return <Layout title="Hasura CRUD">Error: {error.message}</Layout>
  return (
    <Layout title="Hasura CRUD">
      <p className="mb-3 font-bold">Hasura CRUD</p>
      <form
        className="flex flex-col justify-center items-center"
        onSubmit={handleSubmit}
      >
        <input
          className="px-3 py-2 border border-gray-300"
          placeholder="New user ?"
          type="text"
          value={editedUser.name}
          onChange={(e) =>
            setEditedUser({ ...editedUser, name: e.target.value })
          }
        />
        <button
          disabled={!editedUser.name}
          className="disabled:opacity-40 my-3 py-1 px-3 text-white bg-indigo-600 hover:bg-indigo-700 rounded-2xl focus:outline-none"
          data-testid="new"
          type="submit"
        >
          {editedUser.id ? 'Update' : 'Create'}
        </button>
      </form>

      {data?.users.map((user) => {
        return (
          <UserItem
            key={user.id}
            user={user}
            setEditedUser={setEditedUser}
            delete_users_by_pk={delete_users_by_pk}
          />
        )
      })}
    </Layout>
  )
}
export default HasuraCRUD

```

</div></details>

<details>
<summary>components/UserItem.tsx</summary><div>


```tsx:components/UserItem.tsx
import { VFC, memo, Dispatch, SetStateAction } from 'react'
import { Users, DeleteUserMutationFn } from '../types/generated/graphql'

interface Props {
  user: {
    __typename?: 'users'
  } & Pick<Users, 'id' | 'name' | 'created_at'>
  delete_users_by_pk: DeleteUserMutationFn
  setEditedUser: Dispatch<
    SetStateAction<{
      id: string
      name: string
    }>
  >
}

export const UserItem: VFC<Props> = memo(
  ({ user, delete_users_by_pk, setEditedUser }) => {
    //console.log('UserItem rendered')
    return (
      <div className="my-1">
        <span className="mr-2">{user.name}</span>
        <span className="mr-2">{user.created_at}</span>
        <button
          className="mr-1 py-1 px-3 text-white bg-green-600 hover:bg-green-700 rounded-2xl focus:outline-none"
          data-testid={`edit-${user.id}`}
          onClick={() => {
            setEditedUser(user)
          }}
        >
          Edit
        </button>
        <button
          className="py-1 px-3 text-white bg-pink-600 hover:bg-pink-700 rounded-2xl focus:outline-none"
          data-testid={`delete-${user.id}`}
          onClick={async () => {
            await delete_users_by_pk({
              variables: {
                id: user.id,
              },
            })
          }}
        >
          Delete
        </button>
      </div>
    )
  }
)

```

</div></details>

ブラウザ確認して、以下にアクセスしてください。

```
http://localhost:3000/hasura-crud
```

以下の処理ができれば成功です

- 以前、Hasura上で作成したユーザーが表示される
- Insert用のInput要素が表示されて、新規ユーザーが作成できる口ができる
- 各ユーザーごとedit, detele処理ができるボタンができて、それが実行できる

![スクリーンショット 2021-11-07 19.25.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/fdc26344-6d18-00f2-e14b-0678f6749e8a.png)

今日のところは以上です。

次回以降で、CRUD各処理の詳細を見てきます。

## アウトプット100本ノック実施中

https://qiita.com/ryosuketter/items/713474dbd009870e4103