diff --git a/.changeset/little-baboons-wave.md b/.changeset/little-baboons-wave.md new file mode 100644 index 00000000..13550cb9 --- /dev/null +++ b/.changeset/little-baboons-wave.md @@ -0,0 +1,5 @@ +--- +'alova': patch +--- + +fix that mismatching cache when items clear in last page diff --git a/packages/client/src/hooks/pagination/usePagination.ts b/packages/client/src/hooks/pagination/usePagination.ts index 897e12f5..609a8214 100644 --- a/packages/client/src/hooks/pagination/usePagination.ts +++ b/packages/client/src/hooks/pagination/usePagination.ts @@ -192,26 +192,24 @@ export default ( isNextPage = falseValue } = payload; - const { e: expireMilliseconds } = getLocalCacheConfigParam(fetchMethod); - // If the cache time is less than or equal to the current time, it means that the cache is not set and the data will no longer be pre-pulled at this time. - // Or there is already a cache and it is not pre-fetched. - if (expireMilliseconds(MEMORY) <= getTime()) { - return falseValue; - } - if (forceRequest) { - return trueValue; - } - if (await queryCache(fetchMethod)) { - return falseValue; - } - const pageCountVal = pageCount.v; const exceedPageCount = pageCountVal ? preloadPage > pageCountVal : isNextPage // If it is judged to preload the next page of data and there is no page count, it is judged by whether the data volume of the last page reaches the page size. ? len(listDataGetter(rawData)) < pageSize.v : falseValue; - return preloadPage > 0 && !exceedPageCount; + const isMatchPageScope = preloadPage > 0 && !exceedPageCount; + + if (!isMatchPageScope) { + return falseValue; + } + + const { e: expireMilliseconds } = getLocalCacheConfigParam(fetchMethod); + const hasCache = await queryCache(fetchMethod); + + // If the cache time is less than or equal to the current time, it means that the cache is not set and the data will no longer be pre-pulled at this time. + // Or there is already a cache and it is not pre-fetched. + return expireMilliseconds(MEMORY) <= getTime() ? falseValue : forceRequest || !hasCache; }; // Preload next page data @@ -425,7 +423,7 @@ export default ( // cache invalidation await invalidatePaginationCache(); - // When the amount of data on the next page does not exceed the page size, the next page is forced to be requested. Because there is a request for sharing, the pull operation needs to be performed asynchronously after interrupting the request. + // When the amount of data on the next page does not exceed the page size, the next page is forced to be requested. Because there is a request for sharing, the fetching needs to be performed asynchronously after interrupting the request. const snapshotItem = getSnapshotMethods(page.v + 1); if (snapshotItem) { const cachedListData = listDataGetter((await queryCache(snapshotItem.entity)) || {}) || []; @@ -528,14 +526,14 @@ export default ( const isLastPageVal = isLastPage.v; const fillingItemsLen = len(fillingItems); + let isLastEmptyPageInNonAppendMode = false; if (fillingItemsLen > 0 || isLastPageVal) { // Delete data at the specified index const newListData = filterItem(data.v, (_, index) => !includes(indexes, index)); // In page turning mode, if it is the last page and all items have been deleted, then turn one page forward. - if (!append && isLastPageVal && len(newListData) <= 0) { - page.v = pageVal - 1; - } else if (fillingItemsLen > 0) { + isLastEmptyPageInNonAppendMode = !append && isLastPageVal && len(newListData) <= 0; + if (!isLastEmptyPageInNonAppendMode && fillingItemsLen > 0) { pushItem(newListData, ...fillingItems); } data.v = newListData; @@ -546,7 +544,11 @@ export default ( updateTotal(-len(indexes)); // The cache of the current page is updated synchronously - return updateCurrentPageCache(); + return updateCurrentPageCache().then(() => { + if (isLastEmptyPageInNonAppendMode) { + page.v = pageVal - 1; + } + }); }); }; /** diff --git a/packages/client/test/react/usePagination.spec.tsx b/packages/client/test/react/usePagination.spec.tsx index 145d4f58..2200e35c 100644 --- a/packages/client/test/react/usePagination.spec.tsx +++ b/packages/client/test/react/usePagination.spec.tsx @@ -552,6 +552,45 @@ describe('react => usePagination', () => { }); }); + test('should turn to previous when datas in last page are removed in preload mode', async () => { + const fetchMockFn = vi.fn(); + const successMockFn = vi.fn(); + render( + res.list, + initialPage: 28, + initialPageSize: 11 + }} + handleExposure={(exposure: any) => { + exposure.onFetchSuccess(fetchMockFn); + exposure.onSuccess(successMockFn); + }} + /> + ); + + await waitFor(async () => { + expect(successMockFn).toHaveBeenCalledTimes(1); + expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([297, 298, 299])); + }); + + fetchMockFn.mockClear(); + fireEvent.click(screen.getByRole('remove0')); + fireEvent.click(screen.getByRole('remove0')); + fireEvent.click(screen.getByRole('remove0')); + + await waitFor(() => { + expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([])); + }); + + // It will turn to the previous page when the last page is empty + await waitFor(() => { + expect(screen.getByRole('page')).toHaveTextContent('27'); + expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify(generateContinuousNumbers(296, 286))); + }); + }); + test('paginated data insert item without preload', async () => { const fetchMockFn = vi.fn(); const successMockFn = vi.fn(); diff --git a/packages/client/test/vue/usePagination.spec.ts b/packages/client/test/vue/usePagination.spec.ts index 5392bc62..f00d92e4 100644 --- a/packages/client/test/vue/usePagination.spec.ts +++ b/packages/client/test/vue/usePagination.spec.ts @@ -741,6 +741,47 @@ describe('vue => usePagination', () => { }); }); + test('should turn to previous when datas in last page are removed in preload mode', async () => { + const fetchMockFn = vi.fn(); + const successMockFn = vi.fn(); + render(Pagination, { + props: { + getter: getter1, + paginationConfig: { + data: (res: any) => res.list, + initialPage: 28, + initialPageSize: 11 + }, + handleExposure: (exposure: any) => { + exposure.onFetchSuccess(fetchMockFn); + exposure.onSuccess(successMockFn); + } + } + }); + + await waitFor(async () => { + expect(successMockFn).toHaveBeenCalledTimes(1); + expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([297, 298, 299])); + }); + + fetchMockFn.mockClear(); + fireEvent.click(screen.getByRole('remove0')); + fireEvent.click(screen.getByRole('remove0')); + fireEvent.click(screen.getByRole('remove0')); + await waitFor(() => { + expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify([])); + }); + + // It will turn to the previous page when the last page is empty + await waitFor(() => { + expect(screen.getByRole('page')).toHaveTextContent('27'); + expect(screen.getByRole('response')).toHaveTextContent(JSON.stringify(generateContinuousNumbers(296, 286))); + }); + + await delay(150); + expect(fetchMockFn).toHaveBeenCalledTimes(1); + }); + // When the data is fetched again but there is no response, the page is turned to the page being fetched. At this time, the interface also needs to be updated. test('should update data when fetch current page', async () => { const fetchMockFn = vi.fn();