はじめに
Supabaseとクラスを組み合わせた処理で
Uncaught TypeError: result.getFormattedDate is not a function
というエラーに遭遇しました。解決までに時間がかかったので、同じエラーで困っている方の参考になればと思い記事にまとめます。
エラーが発生時のソースコード
import { useEffect, useState } from "react";
import { SearchHistory } from "../domain/SearchHistory";
import { fetchUserSearchHistory } from "../service/fetchUserSearchHistory";
export const SubstituteSearch = () => {
const [searchResults, setSearchResults] = useState<SearchHistory[]>([]);
//ユーザーごとの検索履歴を表示する
const userSearchHistory = async () => {
// ユーザーの検索履歴を取得して表示する処理
const userSearchHistory = await fetchUserSearchHistory();
setSearchResults(userSearchHistory);
};
useEffect(() => {
userSearchHistory();
}, []);
return (
<>
<table>
<thead>
<tr>
<th>No</th>
<th>代替したいもの</th>
<th>出力結果</th>
<th>出力した日付</th>
</tr>
</thead>
<tbody>
{searchResults.map((result, index) => (
<tr key={result.id}>
<td>{index + 1}</td>
<td>{result.query}</td>
<td>{result.ai_response}</td>
{/* Supabaseが返すのはただのオブジェクト(クラスのインスタンスではない)*/}
<td>{result.getFormattedDate()}</td>
</tr>
))}
</tbody>
</table>
</>
);
};
export class SearchHistory {
constructor(
public id: number,
public user_id: number,
public query: string,
public ai_response: string,
public created_at: string
) {}
getFormattedDate(): string {
const date = new Date(this.created_at);
const yyyy = date.getFullYear();
const mm = String(date.getMonth() + 1).padStart(2, "0");
const dd = String(date.getDate()).padStart(2, "0");
return `${yyyy}/${mm}/${dd}`;
}
}
//ユーザーごとの検索履歴を取得する関数
export const fetchUserSearchHistory = async () => {
const response = await supabase
.from("search_history")
.select("*")
.eq("user_id", 1)
.limit(5)
if (response.error) {
console.error("Error fetching user search history:", response.error);
return [];
}
console.log("fetchUserSearchHistory response:", response);
return response.data;
};
原因
Supabaseが返すのはただのJSONオブジェクト配列で、SearchHistoryクラスのインスタンスではなかった。
つまり 型が SearchHistory であってもメソッドは使えないため、getFormattedDate()が存在せずエラーになっていました。
解決法
解決の方針はSupabaseから返ったオブジェクトを SearchHistory クラスに変換することでした。
上記のコードのfetchUserSearchHistory.tsにSupabase から取ったオブジェクトを SearchHistory クラスに変換する機能を追加しました。
import { SearchHistory } from "../domain/SearchHistory";
import { SearchHistoryRow } from "../domain/SearchHistoryRow";
import { supabase } from "../utils/supabase";
//ユーザーごとの検索履歴を取得する関数
export const fetchUserSearchHistory = async () => {
const response = await supabase
.from("search_history")
.select("*")
.eq("user_id", 1) // 例としてuser_idが1のユーザーの履歴を取得
.limit(5);
if (response.error) {
console.error("Error fetching user search history:", response.error);
return [];
}
+ //Supabase から取ったオブジェクトを SearchHistory クラスに変換
+ const histories = (response.data || []).map(
+ (history: SearchHistoryRow) =>
+ new SearchHistory(
+ history.id,
+ history.user_id,
+ history.query,
+ history.ai_response,
+ history.created_at
+ )
);
- console.log("fetchUserSearchHistory response:", response);
+ console.log("fetchUserSearchHistory response:", histories);
- return response.data;
+ return histories;
};
export type SearchHistoryRow = {
id: number;
user_id: number;
query: string;
ai_response: string;
created_at: string;
};
終わりに
今回わかったのは、Supabaseの戻り値は「ただのデータ(プレーンなオブジェクト)」であり、
そのままではクラスのメソッドを利用できないということでした。
もしドメインクラス(今回の SearchHistory)のメソッドを呼びたい場合は、
必ず new SearchHistory(...)のようにして 自分でインスタンス化する処理を挟む必要があることを理解しました。
参考