React customHooks を利用してロジックとUIを切り離すまでをやってみた（1/5）〜このシリーズの方針〜

## はじめに

React customHooks を利用してロジックとUIを切り離すまでをやってみたシリーズ

今回は、このシリーズの方針について書きます。

## これまで

Hasuraにユーザーのデータと仮定してデータを作成し

https://qiita.com/ryosuketter/items/8b01d53d0609f78c2e0e

https://qiita.com/ryosuketter/items/59a05d4eb5c715287ded

データを外部APIとして取得できるようにして、Next.js（apollo）を使って、クエリやミューテーションできるようにしました

https://qiita.com/ryosuketter/items/9e18295d78dc12a1a7f5

Hasuraから取得したユーザーデータの一覧ページと詳細ページを作りながら、`SG`と`ISR`を学んできました

https://qiita.com/ryosuketter/items/442db08856093e6cfeea

## このシリーズの目次

- [このシリーズの方針](https://qiita.com/ryosuketter/items/1142c47913440369b660)👈 今回はコレ
- customHooks を利用してロジックとUIを切り離すまで
    - [ロジックの作成](https://qiita.com/ryosuketter/items/67b168491fd5a186796e)
    - [コンポーネント（UI）作成](https://qiita.com/ryosuketter/items/1dee37be7e4fe62ef6f0)
    - [不要なレンダリングが走らないための工夫（準備編）](https://qiita.com/ryosuketter/items/548dbd70ae835fd66dfb)
    - [不要なレンダリングが走らないための工夫（検証 & 改善編）](https://qiita.com/ryosuketter/items/4fb9b560b0d076491d91) 

## 最終的なゴール

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

![スクリーンショット 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)

### 目的（最終的なゴールを達成する）

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

## このシリーズの方針

Next.js（apollo）からHasuraに対してCRUD操作（クエリやミューテーション）をできるようにしました。

表側は特に問題ないかもですが

![スクリーンショット 2021-11-28 0.15.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14191/8d99ef91-4bd1-299a-dc2a-399be6bbc1a6.png)

コードを見てもらうと課題点もあります。

<details><summary>クリックすると実際のコードが見れます</summary><div>

```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'
import { BreadcrumbJsonLd } from 'next-seo'

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">
      <BreadcrumbJsonLd
        itemListElements={[
          {
            position: 1,
            name: 'Books',
            item: 'https://example.com/books',
          },
          {
            position: 2,
            name: 'Authors',
            item: 'https://example.com/books/authors',
          },
          {
            position: 3,
            name: 'Ann Leckie',
            item: 'https://example.com/books/authors/annleckie',
          },
          {
            position: 4,
            name: 'Ancillary Justice',
            item: 'https://example.com/books/authors/ancillaryjustice',
          },
        ]}
      />
      <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>

上記のコードを見ると、以下が混在している状況です。

- ロジック
    - `useQuery`や`useMutation`などのapolloを使った通信処理
    - submitした際の処理（関数`handleSubmit`）
- UI（コンポーネント）

これを、React の customHooksを利用してロジックとUIを切り離すことをこのシリーズではやっていきます。

また、コンポーネントも分離する関係で不要なレンダリングや処理が走るのを防止するために`memo`や`useCallback`を用いたパフォーマンス改善も実装していきます。

## まとめ

今回は、このシリーズの方針について書きました。

## 次回



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

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