UNPKG

@adventurelabs/scout-core

Version:

Core utilities and helpers for Adventure Labs Scout applications

433 lines (432 loc) 16.3 kB
import { useState, useCallback, useMemo, useEffect, useRef } from "react"; import { useGetSessionsInfiniteByHerdQuery, useGetSessionsInfiniteByDeviceQuery, useGetEventsInfiniteByHerdQuery, useGetEventsInfiniteByDeviceQuery, useGetArtifactsInfiniteByHerdQuery, useGetArtifactsInfiniteByDeviceQuery, } from "../store/api"; // ===================================================== // SESSIONS INFINITE SCROLL HOOKS // ===================================================== export const useInfiniteSessionsByHerd = (herdId, options) => { const [pages, setPages] = useState([]); const [currentCursor, setCurrentCursor] = useState(null); const prevHerdIdRef = useRef(); const currentQuery = useGetSessionsInfiniteByHerdQuery({ herdId, limit: options.limit || 20, cursor: currentCursor, supabase: options.supabase, }, { skip: !options.enabled || !herdId, }); // Reset state when herdId changes useEffect(() => { if (prevHerdIdRef.current !== undefined && prevHerdIdRef.current !== herdId && options.enabled && herdId) { setPages([]); setCurrentCursor(null); } prevHerdIdRef.current = herdId; }, [herdId, options.enabled]); // Update pages when new data arrives useEffect(() => { if (currentQuery.data && !currentQuery.isLoading) { setPages((prev) => { const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) || (p.cursor && currentCursor && p.cursor.id === currentCursor.id && p.cursor.timestamp === currentCursor.timestamp)); if (!existingPage) { return [ ...prev, { cursor: currentCursor, data: currentQuery.data.sessions }, ]; } return prev; }); } }, [currentQuery.data, currentQuery.isLoading, currentCursor]); const loadMore = useCallback(() => { if (currentQuery.data?.hasMore && currentQuery.data.nextCursor && !currentQuery.isLoading) { setCurrentCursor(currentQuery.data.nextCursor); } }, [currentQuery.data, currentQuery.isLoading]); const refetch = useCallback(() => { setPages([]); setCurrentCursor(null); currentQuery.refetch(); }, [currentQuery]); // Flatten all pages into single array const allItems = useMemo(() => { return pages.flatMap((page) => page.data); }, [pages]); return { items: allItems, isLoading: currentQuery.isLoading && pages.length === 0, isLoadingMore: currentQuery.isLoading && pages.length > 0, hasMore: currentQuery.data?.hasMore || false, loadMore, refetch, error: currentQuery.error, }; }; export const useInfiniteSessionsByDevice = (deviceId, options) => { const [pages, setPages] = useState([]); const [currentCursor, setCurrentCursor] = useState(null); const prevDeviceIdRef = useRef(); const currentQuery = useGetSessionsInfiniteByDeviceQuery({ deviceId, limit: options.limit || 20, cursor: currentCursor, supabase: options.supabase, }, { skip: !options.enabled || !deviceId, }); // Reset state when deviceId changes useEffect(() => { if (prevDeviceIdRef.current !== undefined && prevDeviceIdRef.current !== deviceId && options.enabled && deviceId) { setPages([]); setCurrentCursor(null); } prevDeviceIdRef.current = deviceId; }, [deviceId, options.enabled]); useEffect(() => { if (currentQuery.data && !currentQuery.isLoading) { setPages((prev) => { const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) || (p.cursor && currentCursor && p.cursor.id === currentCursor.id && p.cursor.timestamp === currentCursor.timestamp)); if (!existingPage) { return [ ...prev, { cursor: currentCursor, data: currentQuery.data.sessions }, ]; } return prev; }); } }, [currentQuery.data, currentQuery.isLoading, currentCursor]); const loadMore = useCallback(() => { if (currentQuery.data?.hasMore && currentQuery.data.nextCursor && !currentQuery.isLoading) { setCurrentCursor(currentQuery.data.nextCursor); } }, [currentQuery.data, currentQuery.isLoading]); const refetch = useCallback(() => { setPages([]); setCurrentCursor(null); currentQuery.refetch(); }, [currentQuery]); const allItems = useMemo(() => { return pages.flatMap((page) => page.data); }, [pages]); return { items: allItems, isLoading: currentQuery.isLoading && pages.length === 0, isLoadingMore: currentQuery.isLoading && pages.length > 0, hasMore: currentQuery.data?.hasMore || false, loadMore, refetch, error: currentQuery.error, }; }; // ===================================================== // EVENTS INFINITE SCROLL HOOKS // ===================================================== export const useInfiniteEventsByHerd = (herdId, options) => { const [pages, setPages] = useState([]); const [currentCursor, setCurrentCursor] = useState(null); const prevHerdIdRef = useRef(); const currentQuery = useGetEventsInfiniteByHerdQuery({ herdId, limit: options.limit || 20, cursor: currentCursor, supabase: options.supabase, }, { skip: !options.enabled || !herdId, }); // Reset state when herdId changes useEffect(() => { if (prevHerdIdRef.current !== undefined && prevHerdIdRef.current !== herdId && options.enabled && herdId) { setPages([]); setCurrentCursor(null); } prevHerdIdRef.current = herdId; }, [herdId, options.enabled]); useEffect(() => { if (currentQuery.data && !currentQuery.isLoading) { setPages((prev) => { const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) || (p.cursor && currentCursor && p.cursor.id === currentCursor.id && p.cursor.timestamp === currentCursor.timestamp)); if (!existingPage) { return [ ...prev, { cursor: currentCursor, data: currentQuery.data.events }, ]; } return prev; }); } }, [currentQuery.data, currentQuery.isLoading, currentCursor]); const loadMore = useCallback(() => { if (currentQuery.data?.hasMore && currentQuery.data.nextCursor && !currentQuery.isLoading) { setCurrentCursor(currentQuery.data.nextCursor); } }, [currentQuery.data, currentQuery.isLoading]); const refetch = useCallback(() => { setPages([]); setCurrentCursor(null); currentQuery.refetch(); }, [currentQuery]); const allItems = useMemo(() => { return pages.flatMap((page) => page.data); }, [pages]); return { items: allItems, isLoading: currentQuery.isLoading && pages.length === 0, isLoadingMore: currentQuery.isLoading && pages.length > 0, hasMore: currentQuery.data?.hasMore || false, loadMore, refetch, error: currentQuery.error, }; }; export const useInfiniteEventsByDevice = (deviceId, options) => { const [pages, setPages] = useState([]); const [currentCursor, setCurrentCursor] = useState(null); const prevDeviceIdRef = useRef(); const currentQuery = useGetEventsInfiniteByDeviceQuery({ deviceId, limit: options.limit || 20, cursor: currentCursor, supabase: options.supabase, }, { skip: !options.enabled || !deviceId, }); // Reset state when deviceId changes useEffect(() => { if (prevDeviceIdRef.current !== undefined && prevDeviceIdRef.current !== deviceId && options.enabled && deviceId) { setPages([]); setCurrentCursor(null); } prevDeviceIdRef.current = deviceId; }, [deviceId, options.enabled]); useEffect(() => { if (currentQuery.data && !currentQuery.isLoading) { setPages((prev) => { const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) || (p.cursor && currentCursor && p.cursor.id === currentCursor.id && p.cursor.timestamp === currentCursor.timestamp)); if (!existingPage) { return [ ...prev, { cursor: currentCursor, data: currentQuery.data.events }, ]; } return prev; }); } }, [currentQuery.data, currentQuery.isLoading, currentCursor]); const loadMore = useCallback(() => { if (currentQuery.data?.hasMore && currentQuery.data.nextCursor && !currentQuery.isLoading) { setCurrentCursor(currentQuery.data.nextCursor); } }, [currentQuery.data, currentQuery.isLoading]); const refetch = useCallback(() => { setPages([]); setCurrentCursor(null); currentQuery.refetch(); }, [currentQuery]); const allItems = useMemo(() => { return pages.flatMap((page) => page.data); }, [pages]); return { items: allItems, isLoading: currentQuery.isLoading && pages.length === 0, isLoadingMore: currentQuery.isLoading && pages.length > 0, hasMore: currentQuery.data?.hasMore || false, loadMore, refetch, error: currentQuery.error, }; }; // ===================================================== // ARTIFACTS INFINITE SCROLL HOOKS // ===================================================== export const useInfiniteArtifactsByHerd = (herdId, options) => { const [pages, setPages] = useState([]); const [currentCursor, setCurrentCursor] = useState(null); const prevHerdIdRef = useRef(); const currentQuery = useGetArtifactsInfiniteByHerdQuery({ herdId, limit: options.limit || 20, cursor: currentCursor, supabase: options.supabase, }, { skip: !options.enabled || !herdId, }); // Reset state when herdId changes useEffect(() => { if (prevHerdIdRef.current !== undefined && prevHerdIdRef.current !== herdId && options.enabled && herdId) { setPages([]); setCurrentCursor(null); } prevHerdIdRef.current = herdId; }, [herdId, options.enabled]); useEffect(() => { if (currentQuery.data && !currentQuery.isLoading) { setPages((prev) => { const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) || (p.cursor && currentCursor && p.cursor.id === currentCursor.id && p.cursor.timestamp === currentCursor.timestamp)); if (!existingPage) { return [ ...prev, { cursor: currentCursor, data: currentQuery.data.artifacts }, ]; } return prev; }); } }, [currentQuery.data, currentQuery.isLoading, currentCursor]); const loadMore = useCallback(() => { if (currentQuery.data?.hasMore && currentQuery.data.nextCursor && !currentQuery.isLoading) { setCurrentCursor(currentQuery.data.nextCursor); } }, [currentQuery.data, currentQuery.isLoading]); const refetch = useCallback(() => { setPages([]); setCurrentCursor(null); currentQuery.refetch(); }, [currentQuery]); const allItems = useMemo(() => { return pages.flatMap((page) => page.data); }, [pages]); return { items: allItems, isLoading: currentQuery.isLoading && pages.length === 0, isLoadingMore: currentQuery.isLoading && pages.length > 0, hasMore: currentQuery.data?.hasMore || false, loadMore, refetch, error: currentQuery.error, }; }; export const useInfiniteArtifactsByDevice = (deviceId, options) => { const [pages, setPages] = useState([]); const [currentCursor, setCurrentCursor] = useState(null); const prevDeviceIdRef = useRef(); const currentQuery = useGetArtifactsInfiniteByDeviceQuery({ deviceId, limit: options.limit || 20, cursor: currentCursor, supabase: options.supabase, }, { skip: !options.enabled || !deviceId, }); // Reset state when deviceId changes useEffect(() => { if (prevDeviceIdRef.current !== undefined && prevDeviceIdRef.current !== deviceId && options.enabled && deviceId) { setPages([]); setCurrentCursor(null); } prevDeviceIdRef.current = deviceId; }, [deviceId, options.enabled]); useEffect(() => { if (currentQuery.data && !currentQuery.isLoading) { setPages((prev) => { const existingPage = prev.find((p) => (p.cursor === null && currentCursor === null) || (p.cursor && currentCursor && p.cursor.id === currentCursor.id && p.cursor.timestamp === currentCursor.timestamp)); if (!existingPage) { return [ ...prev, { cursor: currentCursor, data: currentQuery.data.artifacts }, ]; } return prev; }); } }, [currentQuery.data, currentQuery.isLoading, currentCursor]); const loadMore = useCallback(() => { if (currentQuery.data?.hasMore && currentQuery.data.nextCursor && !currentQuery.isLoading) { setCurrentCursor(currentQuery.data.nextCursor); } }, [currentQuery.data, currentQuery.isLoading]); const refetch = useCallback(() => { setPages([]); setCurrentCursor(null); currentQuery.refetch(); }, [currentQuery]); const allItems = useMemo(() => { return pages.flatMap((page) => page.data); }, [pages]); return { items: allItems, isLoading: currentQuery.isLoading && pages.length === 0, isLoadingMore: currentQuery.isLoading && pages.length > 0, hasMore: currentQuery.data?.hasMore || false, loadMore, refetch, error: currentQuery.error, }; }; // ===================================================== // INTERSECTION OBSERVER HOOK FOR AUTO-LOADING // ===================================================== export const useIntersectionObserver = (callback, options = {}) => { const [element, setElement] = useState(null); useEffect(() => { if (!element) return; const observer = new IntersectionObserver((entries) => { const first = entries[0]; if (first.isIntersecting) { callback(); } }, { threshold: 0.1, ...options }); observer.observe(element); return () => { if (element) { observer.unobserve(element); } }; }, [element, callback, options]); return setElement; };