Amplifyでデータをフィルタリングできないときの対処法

# データがフィルタリングできないのですが？！🤯
Amplifyでデータをフィルタリングしよう！
〜コード修正の巻〜



## 概要
未経験24新卒❤️‍🔥の筆者が、研修で初めて個人アプリ開発をすることになりました。
現在進行形でAmplifyを使用して日記アプリを作成中。
最初にクイックスタート↓を利用してTodoアプリを作成し日記アプリに改修しています。

https://docs.amplify.aws/react/start/quickstart/

指定した日付を参照してデータをフィルタリングして表示したい！のに……
つまづいてしまったので備忘録としてまとめました。
誰かの役に立てれば嬉しいです！


### 何についての記事？誰向け？
* Amplifyで取得したデータをフィルタリングして表示する方法
* AmplifyやTypeScriptが初めての駆け出しエンジニア向け
:::

 今回行いたいデータ処理については公式ドキュメントに記載があるのですが……
 ドキュメントを読んだだけでは実装できな〜〜い！

 修正版のコードや基礎知識をまとめてみたので、筆者と同じようにドキュメントが読めないよ🫠という方でも安心（？）

 有識者の方々のアドバイスもコメントでお待ちしております🫡

公式ドキュメントはこちら↓

https://docs.amplify.aws/react/build-a-backend/data/query-data/

## Amplifyでデータをフィルタリングできない原因は3点あった
原因はいろいろあるが主に3点
* データをフィルタリングする処理が終わる前に次の処理が進んでしまった
→Promise, await
* filter条件を```eq: "2024-12-05"```などとしていてDBの値と一致していなかった

* 非同期処理などの基礎知識の理解不足

### 改修前のコード：何を考えていたか
```Typescript:Diary.tsx
const Diary: React.FC = () => {
    const [todos, setContent] = useState<Array<Schema["Todo"]["type"]>>([]);
    const targetDate = "2024-11-18";

    const result = client.models.Todo.list({
        filter: {
           createdAt: { eq: targetDate }, // targetDate に一致するデータを取得
            },
        });
```
やろうとしたこと
* tagetDateに一旦固定で日付を設定
* resultでフィルター 
* createdAtがtagetDateと一致するデータを取得する
### 修正したらフィルタリングできた
```Typescript:Diary.tsx
const Diary: React.FC<CalenderProps> = ({ date }) => {
    const [todos, setContent] = useState<Array<Schema["Todo"]["type"]>>([]);
    async function fetchTodos() {
        const targetDate = "2024-11-27"
        const result = await client.models.Todo.list(
            {
                filter: {
                    createdAt: {beginsWith: targetDate},
                },
            }
        );
        console.log("=== " + JSON.stringify(result))
        setContent([...result.data]);
    }
```

### 修正点

#### ① client.models.Todo.listにawaitをつける

→DBからデータを取得するメソッド
戻り値がPromiseなので処理が完了するまで待つ必要がある
→Promiseって？
Promiseの処理が完了するまで待つには```await```をつけて実行
```await```を使用するには```async```で囲むことが必須！

------

#### ② {beginsWith: targetDate}でフィルターする

```targetDate```にはxxxx-xx-xxという値で日付情報が入る。これを使用してフィルタリングする
しかしDBに入っている日付のデータは"2024-11-18T08:59:21.189Z” なので```eq```だとヒットしなくてフィルターできない
→```{beginsWith: targetDate} ```にすることで指定した日付で部分検索？ができる！

---------

#### ③ filter結果resultを確認できるようにする
```console.log("=== " + JSON.stringify(result))result```の中身をコンソールに出力
```
{"data":[{"id":"＊＊＊＊＊＊","content":"あ！","comment":null,"createdAt":"2024-11-27T09:47:52.337Z","updatedAt":"2024-11-27T09:47:52.337Z","owner":"＊＊＊＊＊＊"}],"nextToken":null}
```

-------

#### ④ フィルタリング結果を表示
```setContent([...result.data]);```でフィルターされた結果をTodosに保存
→表示したいときは```Todos.content```などで指定できるよ

## 初心者必見！Amplifyでデータをフィルタリングするために必要な超基礎知識
### client.models.Todo.list とはDBからデータを取得するメソッド
AWS AmplifyのDatastoreまたはGraphQL APIを使用してデータベースからデータを取得するためのメソッド
#### client
Amplifyのクライアントオブジェクト。これを通じてAmplifyが生成したデータモデルにアクセスする
```Typescript:Diary.tsx
import { type Schema } from '../amplify/data/resource';
const client = generateClient<Schema>();
//（↑ここで宣言したclient）
```
resourceファイルで定義したDB情報をインポート
```Typescript:../amplify/data/resource
const schema = a.schema({
 Todo: a.model({
   content: a.string(),
   comment: a.string(),
 }).authorization(allow =>  [allow.owner()]),
```
generateClient<Schema>()ではSchemaという型のデータを渡してその構造に合わせた機能を利用できるようにクライアントを生成している
→このおかげでデータを操作するためのメソッドが利用できるよ！

#### models
データの構造を予め決めている
```
models: ModelTypes<{
…
Todo: ModelType<SetTypeSubArg<{
               fields: {
                   content: ModelField<Nullable<string>, never, undefined>;
                   comment: ModelField<Nullable<string>, never, undefined>;
               };
…
```
今回は上記のようにcontentとcommentには文字列が入りかつnullを許容するということを定義している

#### Todo
モデルに基づいた実際のデータの集合
DB名が入る

#### list
listはTodoモデルのデータをリスト形式で取得する操作
他にも色々な操作ができるメソッドがある

| メソッド | 機能 |
|:-|:-|
| list  | データ一覧を取得  |
|  get | 指定したIDのデータを取得  |
| create  |  新しいデータの登録 |
| update  |  既存データの更新 |
| delete  | 既存データの削除  |
| query  | GraphQLクエリを実行し、データを検索  |
|  observe | データの変更をリアルタイムで監視  |

### Promiseで非同期処理の結果を管理しよう
javascriptは非同期言語であるため、実行完了を待たずに次の処理が行われる 
→書かれた通りに処理をしてくれない

同期言語：書かれた通りに処理を進める。

関数が実行されている間はプログラムが無反応になる
Promiseを使うことで処理の順序を指定できる
#### PromiseStatus
| ステータス | 意味 |
|:-|:-|
| pending  | 未解決（非同期処理がまだ完了していない）  |
| resolved  |  解決済み（正常に完了し結果が利用可能） |
| rejected  | 拒否（失敗しエラーが発生）  |

今回のコードの```client.models.Todo.list()```では、データベースにリクエストを送り、データが返ってくるまでPending状態。
```result```にはPromiseオブジェクトが格納される→データが利用不可のままコードが進んでしまう

どうする？ →awaitを使う：データをデータを取得するまで待ってくれる！

### await, asyncで結果が出るまで待とう
Promiseによる非同期処理をより簡潔に効率よく記述できる！

#### Promiseチェーン
```Typescript
client.models.Todo.list({
  filter: {
    createdAt: { beginsWith: targetDate },
  },
})
  .then((result) => {
    console.log("=== フィルター結果: ", JSON.stringify(result));
    // 必要に応じて他の処理を追加
  })
  ```
client.models.Todo.listはPromiseを返すためthenを使って結果を処理 
これだけだとあまり変わらないように見えるが、複数の非同期処理を書こうとすると可読性が低下する

#### await
```Typescript
const result = await client.models.Todo.list({
		filter: {
				createdAt: {beginsWith: targetDate},
	},
 });
	console.log("=== " + JSON.stringify(result))
 ```
同期処理のように書けるため可読性が高い！
async：関数の頭につけることでPromiseオブジェクトを返す関数にできる
await：Promiseオブジェクトが値を返すのを待つ演算子（asyncの中で使う！）

### 非同期関数で他の処理も同時で実行しよう
同期関数：長時間実行される関数の場合、他の操作が一切できなくなる
その点非同期関数は
* 関数が処理を開始してすぐに値を返す→プログラムが他の処理を実行できる
* 最終的に処理が完了したらその結果を通知

と便利

## まとめ
公式ドキュメント、色々な記事を読んでも、AIに聞いても解決しないことってたくさんありますよね。
今回の処理も結局一人では無理で先輩にアドバイスをもらってやっと動きました😗

初歩的なことではあるんですが、返り値の理解とか、どこで使うべきかちゃんと理解できていなかったのが原因だと思います。特にPromiseや日付の処理について曖昧なまま進めてしまいました。

本記事ではドキュメントを読み解くための超❗初心者向け基礎知識を整理しました。
この内容を調べたことで、少しドキュメントを読めるようになった……🦆
 
記事のタイトルでは大げさに「〜〜の方法！」とうたっていますが、実際には開発中につまづいた部分を備忘録的にまとめたものです。どなたかのお役に立てば幸いです💗

## おまけ
```Typescript:Diary.tsx
    async function fetchTodos() {
        const targetDate = "2024-11-27"
        const result = await client.models.Todo.list(
            {
                filter: {
                    createdAt: {beginsWith: targetDate},
                },
            }
        );
        console.log("=== " + JSON.stringify(result))
        // setContent([...result.data]);
    }
    fetchTodos()
```
#### レンダリングとは何なのか？を理解できた失敗
```// setContent([...result.data]);```のコメントアウトを解除すると```console.log("=== " + JSON.stringify(result))``が大量に表示されるようになる 
→fetchTodos 関数の実行が 無限ループ のように繰り返されている

```setContent([...result.data]) ```の呼び出しによって、React コンポーネントが再レンダリングされ、その中で fetchTodos が再度実行される構造

fetchTodosがコンポーネントのレンダリング中に呼びだされている
→直接関数を呼び出すように書いているから

setContentが状態を更新するたびにコンポーネントが再レンダリングされてfetchTodosが再実行される無限ループに 
→なぜsetContentが状態を更新し続けてしまうのか？

### これ！
状態の更新が再レンダリングを引き起こす
fetchTodos関数が実行される
→setContentが呼び出される
→コンポーネントが再レンダリングされる
→fetchTodos関数が実行される
→以下無限ループ

❤️‍🔥 **useEffect内に```fetchTodos```を記述することで解決** ❤️‍🔥