import { useState, useEffect, useRef, useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; import type { OperationVariables } from '@apollo/client'; import type { Subscribe, Pagination, Load } from '../lib/generic-data-provider'; /** * * @param dataProvider subscribe function created by makeDataProvider * @param update optional function called on each delta received in subscription, if returns true updated data will be not passed from hook (component handles updates internally) * @param variables optional * @returns state: data, loading, error, methods: flush (pass updated data to update function without delta), restart: () => void}}; */ export function useDataProvider({ dataProvider, update, insert, variables, }: { dataProvider: Subscribe; update?: ({ delta, data }: { delta: Delta; data: Data }) => boolean; insert?: ({ insertionData, data, totalCount, }: { insertionData: Data; data: Data; totalCount?: number; }) => boolean; variables?: OperationVariables; }) { const client = useApolloClient(); const [data, setData] = useState(null); const [totalCount, setTotalCount] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState(undefined); const flushRef = useRef<(() => void) | undefined>(undefined); const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined); const loadRef = useRef | undefined>(undefined); const initialized = useRef(false); const flush = useCallback(() => { if (flushRef.current) { flushRef.current(); } }, []); const reload = useCallback((force = false) => { if (reloadRef.current) { reloadRef.current(force); } }, []); const load = useCallback((pagination: Pagination) => { if (loadRef.current) { return loadRef.current(pagination); } return Promise.reject(); }, []); const callback = useCallback( ({ data, error, loading, delta, insertionData, totalCount }) => { setError(error); setLoading(loading); if (!error && !loading) { // if update or insert function returns true it means that component handles updates // component can use flush() which will call callback without delta and cause data state update if (initialized.current) { if (delta && update && update({ delta, data })) { return; } if ( insertionData && insert && insert({ insertionData, data, totalCount }) ) { return; } } initialized.current = true; setTotalCount(totalCount); setData(data); } }, [update, insert] ); useEffect(() => { const { unsubscribe, flush, reload, load } = dataProvider( callback, client, variables ); flushRef.current = flush; reloadRef.current = reload; loadRef.current = load; return unsubscribe; }, [client, initialized, dataProvider, callback, variables]); return { data, loading, error, flush, reload, load, totalCount }; }