import { useState, useEffect, useRef, useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; import type { OperationVariables } from '@apollo/client'; import type { Subscribe, Load, UpdateCallback, } 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, noUpdate, skip, }: { dataProvider: Subscribe; update?: ({ delta, data }: { delta: Delta; data: Data }) => boolean; insert?: ({ insertionData, data, totalCount, }: { insertionData: Data; data: Data; totalCount?: number; }) => boolean; variables?: OperationVariables; noUpdate?: boolean; skip?: boolean; }) { const client = useApolloClient(); const [data, setData] = useState(null); const [totalCount, setTotalCount] = useState(); const [loading, setLoading] = useState(!skip); 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>((...args) => { if (loadRef.current) { return loadRef.current(...args); } return Promise.reject(); }, []); const callback = useCallback>( ({ data, error, loading, delta, insertionData, totalCount, isInsert, isUpdate, }) => { 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 && data) { if ( isUpdate && !noUpdate && update && (!delta || update({ delta, data })) ) { return; } if ( isInsert && insert && (!insertionData || insert({ insertionData, data, totalCount })) ) { return; } } initialized.current = true; setTotalCount(totalCount); setData(data); } }, [update, insert, noUpdate] ); useEffect(() => { if (skip) { return; } 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, skip]); return { data, loading, error, flush, reload, load, totalCount }; }