diff --git a/hooks/useLoadData/useLoadData.test.ts b/hooks/useLoadData/useLoadData.test.ts index 92779ed..99d1830 100644 --- a/hooks/useLoadData/useLoadData.test.ts +++ b/hooks/useLoadData/useLoadData.test.ts @@ -383,4 +383,63 @@ describe('useLoadData', () => { expect(result.current.isError).toBe(true); expect(result.current.error).toBe('immediate failure'); }); + + it('should re-invoke fetchData when fetchWhenDepsChange with an initial promise with normal dep', async () => { + const {result} = renderHook(() => { + const [dep, setDep] = useState('a'); + const loadedData = useLoadData(getSuccess, [dep], {fetchWhenDepsChange: true}); + + return {loadedData, setDep}; + }); + expect(result.current.loadedData.isInProgress).toBe(true); + + await waitFor(() => expect(result.current.loadedData.isInProgress).toBe(false)); + expect(getSuccess).toHaveBeenCalledWith('a'); + expect(getSuccess).toHaveBeenCalledTimes(1); + + await act(() => result.current.setDep('b')); + await waitFor(() => expect(result.current.loadedData.isInProgress).toBe(false)); + expect(getSuccess).toHaveBeenCalledTimes(2); + + expect(getSuccess).toHaveBeenCalledWith('b'); + }); + + it('should re-invoke fetchData when fetchWhenDepsChange with an initial promise with finished dependency', async () => { + const {result} = renderHook(() => { + const [dep, setDep] = useState({...successfulResponse, result: 'a'}); + const loadedData = useLoadData(getSuccess, [dep], {fetchWhenDepsChange: true}); + + return {loadedData, setDep}; + }); + expect(result.current.loadedData.isInProgress).toBe(true); + + await waitFor(() => expect(result.current.loadedData.isInProgress).toBe(false)); + expect(getSuccess).toHaveBeenCalledWith('a'); + expect(getSuccess).toHaveBeenCalledTimes(1); + + await act(() => result.current.setDep({...successfulResponse, result: 'b'})); + await waitFor(() => expect(result.current.loadedData.isInProgress).toBe(false)); + expect(getSuccess).toHaveBeenCalledTimes(2); + expect(getSuccess).toHaveBeenCalledWith('b'); + }); + + it('should re-invoke fetchData when fetchWhenDepsChange with an initial promise with pending dependency', async () => { + const {result} = renderHook(() => { + const [dep, setDep] = useState(pendingResponse); + const loadedData = useLoadData(getSuccess, [dep], {fetchWhenDepsChange: true}); + + return {loadedData, setDep}; + }); + expect(result.current.loadedData.isInProgress).toBe(true); + await act(() => result.current.setDep({...successfulResponse, result: 'a'})); + + await waitFor(() => expect(result.current.loadedData.isInProgress).toBe(false)); + expect(getSuccess).toHaveBeenCalledWith('a'); + expect(getSuccess).toHaveBeenCalledTimes(1); + + await act(() => result.current.setDep({...successfulResponse, result: 'b'})); + await waitFor(() => expect(result.current.loadedData.isInProgress).toBe(false)); + expect(getSuccess).toHaveBeenCalledTimes(2); + expect(getSuccess).toHaveBeenCalledWith('b'); + }); }); diff --git a/hooks/useLoadData/useLoadData.ts b/hooks/useLoadData/useLoadData.ts index ed4a8a7..b6c6e85 100644 --- a/hooks/useLoadData/useLoadData.ts +++ b/hooks/useLoadData/useLoadData.ts @@ -162,7 +162,14 @@ export function useLoadData( // eslint-disable-next-line @typescript-eslint/promise-function-async const initialPromise = useMemo(() => { const correctedArgs = correctOptionalDependencies(fetchDataArgs); - if (!data && counter < 1 && checkArgsAreLoaded(correctedArgs)) { + // initialPromise should NOT be defined in the following scenarios: + // 1. data is passed, in which case fetchData should never be invoked + // 2. dependencies are not ready initially, so we cannot proceed with calling fetchData + // 3. we are attempting to retry calling fetchData, and we do not want initialPromise to interfere + // with re-invoking fetchData + // 4. we are attempting to refetch data due to dependencies changing, and we do not want initialPromise + // to interfere with re-invoking fetchData + if (!data && counter < 1 && checkArgsAreLoaded(correctedArgs) && !localFetchWhenDepsChange) { try { return { res: fetchData(...((correctedArgs.map(unboxApiResponse) || []) as Parameters)), @@ -177,7 +184,7 @@ export function useLoadData( } else { return {res: undefined, error: undefined}; } - }, [counter]); + }, [counter, localFetchWhenDepsChange]); const nonPromiseResult = initialPromise.res instanceof Promise ? undefined : initialPromise.res; const initialData = data || nonPromiseResult;