【個人開発】JISOU 課題4 デジタル名刺アプリを完成させた上での苦労と気づき

# はじめに

お疲れ様です、りつです。

React×TypeScriptで実装していたデジタル名刺アプリが完成いたしました。

今回は、作成したアプリのご紹介、および完成に至るまでの苦労や学びを共有できればと思います。

:::note info
ご参考までに、以下が歴代の課題の記事です。
:::


https://qiita.com/ritsu21ctws/items/ae4a8302d4748ef5bcc0

https://qiita.com/ritsu21ctws/items/d8838e056ae07af9942d

https://qiita.com/ritsu21ctws/items/364ed26a3d54ea0e8b5f

# 🎨 成果物について

オンライン上でユーザーが名詞を登録・閲覧できるアプリを作成しました。

## アプリURL

https://business-card-acec2.web.app/

## GitHubリポジトリ

https://github.com/ritsu21ctws/business-card

## 画面一覧

| No | 画面名 | 機能 |
| :-- |:-----------|:------------|
| 1 | TOP画面 | 名刺の検索 |
| 2 | 名刺登録画面 | 名刺の登録（バリデーションあり） |
| 3 | 名刺閲覧画面 | 名刺の閲覧 |

### 画面の画像

| TOP画面 | 名刺登録画面 | 名刺閲覧画面 |
| :--: |:----------:|:-----------:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/ddf9f50b-60ac-2134-dbec-2ce976b6251f.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/bae849b4-7665-3621-a274-a6581f801a10.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/f4d8861d-d585-b83b-472c-8604353727f3.png) |



## 使用技術

- Vite
- Chakra UI
- React
- React Hook Form
- TypeScript
- Jest / Testing Library (テスト)
- Supabase (DB)
- Firebase (ホスティング)
- GitHub Actions (CI/CD)
- React Router

# ⤴️ 以前と比べてみて

前回までと異なる部分として、以下の3点があげられます。

1. `React Router`を用いたページ遷移
2. Supabase上で多対多のテーブル構造を利用
3. データ削除バッチの実装と、GitHubActionsによる日次実行

## 1. `React Router`を用いたページ遷移

以前作成した学習記録アプリは、1画面でCRUD（データの登録・表示・編集、削除）が行えましたが、今回実装したデジタル名刺アプリでは画面が3つ存在します。

そのため、各画面は`React Router`を用いて遷移するように実装しました。

## 2. Supabase上で多対多のテーブル構造を利用

デジタル名刺アプリでは、名刺登録時にマルチセレクトで「好きな技術」を複数選択できるように実装しています。

![レコーディング-2025-02-02-134921.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/5a98f2e5-b0fd-a422-bf4d-c01d963c679b.gif)

名刺を表示する際には、登録した「好きな技術」はカンマ区切りで表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/f4d8861d-d585-b83b-472c-8604353727f3.png)

マルチセレクトの実装にあたり、Supabase上では以下のように3つのテーブルを用意し、`user_skill`テーブルを中間テーブルとして利用することで、1ユーザーに対して複数の好きな技術を登録、表示を行えるようにしました。

| テーブル名 | 用途 |
|:-----------|:------------|
| users | 名刺に登録されたユーザー情報の管理 |
| skills | 名刺登録時に「好きな技術」欄に表示するためのデータを管理 |
| user_skill | ユーザーと、そのユーザーが選択したスキルを管理する中間テーブル |

なお、外部キーの設定を行う際、`ON DELETE CASCADE`の設定も合わせて実施しました。

この設定を行うことで、`users`テーブルが削除された場合には紐づく`user_skill`テーブルのデータも自動で削除するようにしています。

これにより、データの整合性を保つことができます。

## 3. データ削除バッチの実装と、GitHubActionsによる日次実行

今回Reactで初めてバッチ処理を実装しました。

具体的には、前日作成した名刺データ（`users`と`user_skill`データ）を削除するバッチを実装し、そのバッチを日本時間のAM6:00に実行するようにGitHubActionsに設定しました。

# 💡 苦労や気づき

1. Supabaseの多対多の設定
2. フォームのマルチセレクト
3. 削除対象抽出時のタイムゾーンの差異の調整

## 1. Supabaseの多対多の設定

今回初めてSupabaseで多対多のテーブル構成を採用したのですが、実装時にリレーション先のデータがうまく取得できず、時間がかかってしまいました。

最終的には以下の記事に記載の内容で設定したところ、無事リレーション先データを取得することができるようになりました。

https://qiita.com/ritsu21ctws/items/e831eb3c19c4c75bc3f5

## 2. フォームのマルチセレクト

名刺登録時に、「好きな技術」欄で複数のスキル（例えばReactであったり、TypeScriptであったり）を選択できるようにしています。

マルチセレクト自体は`Chakra UI`で簡単に表示はできるのですが、`React Hook Form`と組み合わせた際に、複数選択された値をテーブルに登録する際の方法などがわからなかったので調べながら実装を進めました。

また、マルチセレクトのテスト実装も初めてでしたが、以前までのテストの応用で実装できました。

React Testing Libraryは画面上の操作を模した書き方ができるので、テストコードの実装時はもちろん、テストコードを読む際もわかりやすいことが改めてメリットとして感じました。

## 3. 削除対象抽出時のタイムゾーンの差異の調整

日次バッチでは、朝の6時に前日分のデータ削除を行う必要があります。

しかし、Supabaseの日時カラムのタイムゾーンが`UTC`のため、登録した日本時間と、実際にテーブルカラムに登録される時間が9時間分ズレてしまうことにより、削除時に意図しないデータが削除されてしまう問題が発生しました。

例えば、1月3日の朝6時にバッチが起動する際、下表の4件のデータのうち、1～3が削除対象とならなければいけませんが、以下のSQLを実行すると、実際に削除されるのは2～4のデータとなります。

```sql
DELETE FROM public.users
    WHERE created_at >= (date_trunc('day', now()) - interval '1 day')
    AND created_at < date_trunc('day', now());
```

| No | データ登録を行った日時 | Supabaseに登録された値 | 1月3日のバッチで削除されるべきデータか |　SQLによって削除対象とされるか否か |
|:-----------|:-----------|:-----------|:----------:|:-----------:|
| 1 | 1/2 8:59:59 | 1/1 23:59:59 | 〇 | × |
| 2 | 1/2 9:00:00 | 1/2 0:00:00 | 〇 | 〇 |
| 3 | 1/2 23:59:59 | 1/2 14:59:59 | 〇 | 〇 |
| 4 | 1/3 8:59:59 | 1/2 23:59:59 | × | 〇 |

今回は、SQLで条件を指定する際にタイムゾーンを指定してあげることで、想定されるデータを削除することが可能となりました。

タイムゾーンの部分はなかなか難しいところですが、Supabaseではタイムゾーンはデフォルトの`UTC`のままにしておくことが強く推奨されているようなので、上記のようにSQLの方の調整で対応することにしました。

https://supabase.com/docs/guides/database/postgres/configuration#managing-timezones

> **Managing timezones**
> Every Supabase database is set to UTC timezone by default. We strongly recommend keeping it this way, even if your users are in a different location.
This is because it makes it much easier to calculate differences between timezones if you adopt the mental model that "everything in my database is in UTC time."

日時で条件を絞り込んだり、画面に日時を表示させるときなどはタイムゾーンによる時間のずれを考慮していく必要があります。

# おわりに

前回と比べて実装自体は比較的スムーズに進められたように思いますが、新しい機能を取り入れるとそれなりにエラーや設定でハマってしまいますね。

繰り返しアプリ実装を繰り返すことで、少しずつ環境構築～実装～CI/CDにも慣れてきたことを実感しています。

次回の課題では、自分で考えたオリジナルアプリを実装していきます！

おそらく今まで以上に躓くことも多くなりそうですが、ここまで頑張ってきた集大成となりますので、完成まで頑張っていきたいと思います！

# 参考

https://business-card-acec2.web.app/

https://github.com/ritsu21ctws/business-card

# JISOUのメンバー募集中！
プログラミングコーチングJISOUでは、新たなメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか？
興味のある方は、ぜひホームページをのぞいてみてくださ！
▼▼▼

https://projisou.jp