データがフィルタリングできないのですが?!🤯
Amplifyでデータをフィルタリングしよう!
〜コード修正の巻〜
概要
未経験24新卒❤️🔥の筆者が、研修で初めて個人アプリ開発をすることになりました。
現在進行形でAmplifyを使用して日記アプリを作成中。
最初にクイックスタート↓を利用してTodoアプリを作成し日記アプリに改修しています。
指定した日付を参照してデータをフィルタリングして表示したい!のに……
つまづいてしまったので備忘録としてまとめました。
誰かの役に立てれば嬉しいです!
何についての記事?誰向け?
- 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の値と一致していなかった -
非同期処理などの基礎知識の理解不足
改修前のコード:何を考えていたか
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と一致するデータを取得する
修正したらフィルタリングできた
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が生成したデータモデルにアクセスする
import { type Schema } from '../amplify/data/resource';
const client = generateClient<Schema>();
//(↑ここで宣言したclient)
resourceファイルで定義したDB情報をインポート
const schema = a.schema({
Todo: a.model({
content: a.string(),
comment: a.string(),
}).authorization(allow => [allow.owner()]),
generateClient()では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チェーン
client.models.Todo.list({
filter: {
createdAt: { beginsWith: targetDate },
},
})
.then((result) => {
console.log("=== フィルター結果: ", JSON.stringify(result));
// 必要に応じて他の処理を追加
})
client.models.Todo.listはPromiseを返すためthenを使って結果を処理
これだけだとあまり変わらないように見えるが、複数の非同期処理を書こうとすると可読性が低下する
await
const result = await client.models.Todo.list({
filter: {
createdAt: {beginsWith: targetDate},
},
});
console.log("=== " + JSON.stringify(result))
同期処理のように書けるため可読性が高い!
async:関数の頭につけることでPromiseオブジェクトを返す関数にできる
await:Promiseオブジェクトが値を返すのを待つ演算子(asyncの中で使う!)
非同期関数で他の処理も同時で実行しよう
同期関数:長時間実行される関数の場合、他の操作が一切できなくなる
その点非同期関数は
- 関数が処理を開始してすぐに値を返す→プログラムが他の処理を実行できる
- 最終的に処理が完了したらその結果を通知
と便利
まとめ
公式ドキュメント、色々な記事を読んでも、AIに聞いても解決しないことってたくさんありますよね。
今回の処理も結局一人では無理で先輩にアドバイスをもらってやっと動きました😗
初歩的なことではあるんですが、返り値の理解とか、どこで使うべきかちゃんと理解できていなかったのが原因だと思います。特にPromiseや日付の処理について曖昧なまま進めてしまいました。
本記事ではドキュメントを読み解くための超❗初心者向け基礎知識を整理しました。
この内容を調べたことで、少しドキュメントを読めるようになった……🦆
記事のタイトルでは大げさに「〜〜の方法!」とうたっていますが、実際には開発中につまづいた部分を備忘録的にまとめたものです。どなたかのお役に立てば幸いです💗
おまけ
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
を記述することで解決 ❤️🔥