Download src/hooks/useRealtimeQuery.ts from Esketch/CarboAny: direct link, hf CLI and curl.
- Browser
- Download file 3.69 kB
-
https://huggingface.co/spaces/Esketch/CarboAny/resolve/main/src/hooks/useRealtimeQuery.ts
- Command line
-
hf download hf://spaces/Esketch/CarboAny/src/hooks/useRealtimeQuery.ts
-
curl -L -o useRealtimeQuery.ts https://huggingface.co/spaces/Esketch/CarboAny/resolve/main/src/hooks/useRealtimeQuery.ts
3.69 kB
| // Generic Realtime subscription hook for Supabase tables | |
| // Fetches initial data then listens for INSERT/UPDATE/DELETE events | |
| import { useEffect, useState, useCallback, useRef } from 'react'; | |
| import { supabase } from '../lib/supabase'; | |
| import type { RealtimeChannel } from '@supabase/supabase-js'; | |
| type Row = { [key: string]: unknown }; | |
| interface UseRealtimeQueryOptions<T> { | |
| table: string; | |
| /** Supabase filter string, e.g. 'user_id=eq.abc123' */ | |
| filter?: string; | |
| /** Column to order by (default: created_at desc) */ | |
| orderBy?: string; | |
| ascending?: boolean; | |
| limit?: number; | |
| /** Map a raw DB row to the UI type */ | |
| mapRow: (row: Row) => T; | |
| /** Unique key extractor (default: row.id) */ | |
| getKey?: (item: T) => string; | |
| enabled?: boolean; | |
| } | |
| interface UseRealtimeQueryResult<T> { | |
| data: T[]; | |
| loading: boolean; | |
| error: string | null; | |
| refetch: () => Promise<void>; | |
| } | |
| export function useRealtimeQuery<T>( | |
| options: UseRealtimeQueryOptions<T>, | |
| ): UseRealtimeQueryResult<T> { | |
| const { | |
| table, | |
| filter, | |
| orderBy = 'created_at', | |
| ascending = false, | |
| limit, | |
| mapRow, | |
| getKey = (item: T) => (item as Row).id as string, | |
| enabled = true, | |
| } = options; | |
| const [data, setData] = useState<T[]>([]); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState<string | null>(null); | |
| const channelRef = useRef<RealtimeChannel | null>(null); | |
| const fetchData = useCallback(async () => { | |
| if (!enabled) return; | |
| setLoading(true); | |
| setError(null); | |
| let query = supabase.from(table).select('*').order(orderBy, { ascending }); | |
| if (limit) query = query.limit(limit); | |
| // Apply simple eq filters from the filter string | |
| if (filter) { | |
| const parts = filter.split('=eq.'); | |
| if (parts.length === 2) { | |
| query = query.eq(parts[0], parts[1]); | |
| } | |
| } | |
| const { data: rows, error: err } = await query; | |
| if (err) { | |
| setError(err.message); | |
| setLoading(false); | |
| return; | |
| } | |
| setData((rows ?? []).map(mapRow)); | |
| setLoading(false); | |
| }, [table, filter, orderBy, ascending, limit, mapRow, enabled]); | |
| useEffect(() => { | |
| if (!enabled) return; | |
| fetchData(); | |
| // Realtime subscription | |
| const channelName = `realtime-${table}-${filter ?? 'all'}`; | |
| const channel = supabase | |
| .channel(channelName) | |
| .on( | |
| 'postgres_changes', | |
| { | |
| event: '*', | |
| schema: 'public', | |
| table, | |
| ...(filter ? { filter } : {}), | |
| }, | |
| (payload) => { | |
| const eventType = payload.eventType; | |
| if (eventType === 'INSERT') { | |
| const newItem = mapRow(payload.new as Row); | |
| setData((prev) => | |
| ascending ? [...prev, newItem] : [newItem, ...prev], | |
| ); | |
| } else if (eventType === 'UPDATE') { | |
| const updatedItem = mapRow(payload.new as Row); | |
| const key = getKey(updatedItem); | |
| setData((prev) => | |
| prev.map((item) => (getKey(item) === key ? updatedItem : item)), | |
| ); | |
| } else if (eventType === 'DELETE') { | |
| const oldId = (payload.old as Row).id as string; | |
| if (oldId) { | |
| setData((prev) => | |
| prev.filter((item) => getKey(item) !== oldId), | |
| ); | |
| } | |
| } | |
| }, | |
| ) | |
| .subscribe(); | |
| channelRef.current = channel; | |
| return () => { | |
| if (channelRef.current) { | |
| supabase.removeChannel(channelRef.current); | |
| channelRef.current = null; | |
| } | |
| }; | |
| }, [table, filter, enabled, ascending, fetchData, mapRow, getKey]); | |
| return { data, loading, error, refetch: fetchData }; | |
| } | |