Supabaseデータフェッチパターン
:LiTarget: 用途
Supabase からデータを取得・キャッシュ・更新する標準パターン。
:LiSparkle: 特徴
- SSRサポート
- リアルタイム購読
- 楽観的更新
- エラーハンドリング
:LiCode: コード(コピペ用)
// === lib/supabase.ts ===
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
// 型安全 fetch ラッパー(エラーハンドリング込み)
export async function dbFetch<T>(
query: () => Promise<{ data: T | null; error: any }>
): Promise<T> {
const { data, error } = await query();
if (error) throw new Error(error.message);
if (!data) throw new Error('No data');
return data;
}
// === hooks/useSupabaseQuery.ts ===
import { useEffect, useState } from 'react';
export function useSupabaseQuery<T>(
key: string,
fetcher: () => Promise<T>,
realtime?: { table: string; filter?: string },
) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
const refetch = async () => {
try { setLoading(true); setData(await fetcher()); setError(null); }
catch (e) { setError(e as Error); }
finally { setLoading(false); }
};
useEffect(() => {
refetch();
if (!realtime) return;
const ch = supabase.channel(`${key}-rt`)
.on('postgres_changes', { event: '*', schema: 'public', table: realtime.table }, () => refetch())
.subscribe();
return () => { supabase.removeChannel(ch); };
}, [key]);
return { data, loading, error, refetch };
}
// 使い方:
// const { data: tasks } = useSupabaseQuery('tasks',
// () => dbFetch(() => supabase.from('tasks').select('*').order('created_at')),
// { table: 'tasks' } // リアルタイム購読
// );
:LiHandPointer: 使い方
対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。
:LiAlertCircle: 注意事項
- 依存パッケージを忘れず追加