はじめに
以前作成した学習記録アプリを改良し、SupabaseというBaaSと接続して、データを永続化するように実装しました。
今回はその時の手順をまとめたいと思います。
↓前回の記事はこちらです
https://qiita.com/Daisuke_Kikukawa/items/f404a53f4e55b2a8e033
制作したアプリ
機能面は変わらないのですが、今回新たにDBに接続しました。
最初は、ローカルでデータを管理していた学習記録アプリを、Supabaseと連携させることで、データを永続的に保存できるようになりました。
これにより、リロードしたりブラウザを閉じても、次回アクセス時にデータが保持されるようになりました。
実装手順
1.Supabaseのセットアップ
Supabaseの公式ウェブサイト(https://supabase.com) にアクセスし、ダッシュボードから「New Project」をクリックします。
プロジェクトのダッシュボードで「Settings」→「API」を選択し、Project URLとanon publicキーをコピーします。
これらがそれぞれのちに設定する環境変数のREACT_APP_SUPABASE_URL
とREACT_APP_SUPABASE_ANON_KEY
になります。
次にReactプロジェクトに環境変数の追加を行います。
ルートディレクトリに.envファイルを作成し、以下の行を追加します。
REACT_APP_SUPABASE_URL=プロジェクトURL
REACT_APP_SUPABASE_ANON_KEY=anon publicキー
Supabaseクライアントライブラリのインストールを行うため、ターミナルで以下のコマンドを実行します。
npm install @supabase/supabase-js
これらの手順を行うことでReactアプリケーションからSupabaseを使用する準備が整います。
これらの設定を使用してSupabaseクライアントを初期化し、データベース操作を実装していきます。
2. Supabaseクライアントの初期化
プロジェクトのsrcディレクトリにsupabase.jsという名前の新しいファイルを作成し、supabase.jsファイルの先頭に以下の行を追加します。
この記述を加えることでSupabaseクライアントを作成するための関数をインポートすることができます。
import { createClient } from '@supabase/supabase-js'
次に.envファイルに設定した環境変数を取得します:
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
最終的に、supabase.js
ファイルは以下のようになります。
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.REACT_APP_SUPABASE_URL
const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
3. データベース操作関数の実装
supabaseFunctions.js
ファイルを作成し、データベース操作のための関数を実装します
データベースを操作するための関数は、アプリケーションとSupabaseデータベース間の橋渡しをする重要な部分になります。
今回は、主要CRUDのうち全件取得、新規作成、削除の実装を行いました。
ファイルの先頭に以下の行を追加して、初期化済みのSupabaseクライアントをインポートします。
import { supabase } from "./supabase.js";
getAllRecords関数の実装
この関数でデータベースから全ての学習記録を取得できるようになります。
export const getAllRecords = async () => {
const records = await supabase.from("study-record").select("*");
return records.data;
};
supabase.from("study-record")
で対象テーブルを指定し、.select("*")
ですべてのカラムを選択しています。
addTodo関数の実装
この関数で新しい学習記録をデータベースに追加ができるようになります。
export const addTodo = async (title, time) => {
await supabase.from("study-record").insert({ title: title, time: time });
};
supabase.from("study-record")
で対象テーブルを指定し、supabase.from("study-record").insert()
で新しいレコードを挿入します。
deleteTodo関数の実装
この関数で指定されたIDの学習記録を削除することができます。
export const deleteTodo = async (id) => {
(await supabase.from("study-record").delete().eq("id", id));
}
supabase.from("study-record").delete()で削除操作を指定し、.eq("id", id)で削除対象のレコードを指定しています。
最終的に、supabaseFunctions.js
ファイルは以下のようになります
mport { supabase } from "./supabase.js";
export const getAllRecords = async () => {
// 実装は上記のとおり
};
export const addTodo = async (title, time) => {
// 実装は上記のとおり
};
export const deleteTodo = async (id) => {
// 実装は上記のとおり
};
データベース操作用の関数を実装することにより、Reactコンポーネントから簡単にデータベース操作を行うことができます。各関数は非同期で実装されており、async/awaitを使用してPromiseベースの操作を行っています。
4. Reactコンポーネントの修正
次に既存のReactコンポーネント(App.js)を修正して、Supabaseを使用するように変更します。
App.js
の先頭に以下の行を追加します
import { useState, useEffect } from "react";
import { getAllRecords, addTodo, deleteTodo } from "../utils/supabaseFunctions";
getAllRecords
,addTodo
,deleteTodo
は、先ほど実装したデータベース操作関数になります。
コンポーネント内で以下の状態変数を設定します
const [isLoading, setIsLoading] = useState(true);
const [records, setRecords] = useState([]);
const [newTitle, setNewTitle] = useState("");
const [newTime, setNewTime] = useState("");
const [error, setError] = useState("");
const [totalTime, setTotalTime] = useState(0);
-
isLoading
: データ取得中かどうかを示すフラグ -
records
: 取得した学習記録のリスト -
newTitle
, newTime: 新規入力用の状態 -
error
: エラーメッセージ保存用 -
totalTime
: 合計学習時間
useEffectを使用したデータの取得
コンポーネントのマウント時にデータを取得するためにuseEffectを使用します
useEffect(() => {
getRecords();
}, []);
const getRecords = async () => {
try {
const fetchedRecords = await getAllRecords();
setRecords(fetchedRecords);
setIsLoading(false);
} catch (error) {
console.error("Error fetching records:", error);
setError("データの取得に失敗しました。");
setIsLoading(false);
}
};
新規追加処理の実装
const handleSubmit = async (e) => {
e.preventDefault();
if (newTitle.trim() === "" || newTime.trim() === "") {
setError("学習内容と学習時間を入力してください");
return;
}
try {
const addedRecord = await addTodo(newTitle, newTime);
if (addedRecord) {
setRecords([addedRecord, ...records]);
setNewTitle("");
setNewTime("");
setError("");
}
} catch (error) {
console.error("Error adding record:", error);
setError("記録の追加に失敗しました。");
}
};
削除機能の実装
const handleDelete = async (id) => {
try {
const success = await deleteTodo(id);
if (success) {
setRecords(records.filter(record => record.id !== id));
}
} catch (error) {
console.error("Error deleting record:", error);
setError("記録の削除に失敗しました。");
}
};
HTMLの実装
以下が取得したデータを表示し、新規追加と削除機能のHTML部分になります
return (
<>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="container">
<h1>学習記録アプリ</h1>
{error && <p className="error">{error}</p>}
<form onSubmit={handleSubmit}>
<input
type="text"
value={newTitle}
onChange={(e) => setNewTitle(e.target.value)}
placeholder="学習内容"
/>
<input
type="number"
value={newTime}
onChange={(e) => setNewTime(e.target.value)}
placeholder="学習時間(時間)"
/>
<button type="submit">追加</button>
</form>
<ul>
{records.map((record) => (
<li key={record.id}>
{record.title} - {record.time}時間
<button onClick={() => handleDelete(record.id)}>削除</button>
</li>
))}
</ul>
<p>合計学習時間: {totalTime}時間</p>
</div>
)}
</>
);
- ローディング中は「Loading...」を表示します。
- エラーメッセージがある場合は表示します。
- フォームで新規記録の入力を受け付けます。
- 記録のリストを表示し、各記録に削除ボタンを付けます。
- 合計学習時間を表示します。
以上が実装部分になります。
大変だったところ
Reactアプリケーションをローカルストレージからデータベースに移行する部分が、想像していたよりも難しかったです。
特に非同期処理の管理が難しかったです。ローカルストレージを使用していた時は、すべて同期的に行えていたのですが、Supabaseを利用することで、データベース操作が非同期になりました。そのため、useEffectを使って実装を行う部分で少し詰まってしまいました。
また、エラーハンドリングの扱いにも苦労しました。ローカルでの保存時では考慮する必要のなかったネットワークエラーやデータベースエラーなど、考えないといけないエラーパターンが多かったです。今後は各データベース操作関数にtry-catch構文を導入し、発生したエラーを適切にキャッチして処理する習慣をつけようと思います。
最後に
DB接続のやり方がわかったことで自分の実装の幅がかなり広がったと感じています!
まだまだ基本的な部分しかわかっていないので、より深めていきたいと思います!
JISOUのメンバー募集中
プログラミングコーチングJISOUではメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
気になる方はぜひHPからライン登録お願いします👇