Skip to content

Commit

Permalink
fix: fix that mismatching cache when items clear in last page (#629)
Browse files Browse the repository at this point in the history
  • Loading branch information
JOU-amjs authored Jan 13, 2025
1 parent 92e83a1 commit 55a9cc0
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-baboons-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'alova': patch
---

fix that mismatching cache when items clear in last page
40 changes: 21 additions & 19 deletions packages/client/src/hooks/pagination/usePagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,26 +192,24 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
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
Expand Down Expand Up @@ -425,7 +423,7 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(
// 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)) || {}) || [];
Expand Down Expand Up @@ -528,14 +526,14 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(

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;
Expand All @@ -546,7 +544,11 @@ export default <AG extends AlovaGenerics, ListData extends unknown[]>(

updateTotal(-len(indexes));
// The cache of the current page is updated synchronously
return updateCurrentPageCache();
return updateCurrentPageCache().then(() => {
if (isLastEmptyPageInNonAppendMode) {
page.v = pageVal - 1;
}
});
});
};
/**
Expand Down
39 changes: 39 additions & 0 deletions packages/client/test/react/usePagination.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Pagination
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)));
});
});

test('paginated data insert item without preload', async () => {
const fetchMockFn = vi.fn();
const successMockFn = vi.fn();
Expand Down
41 changes: 41 additions & 0 deletions packages/client/test/vue/usePagination.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 55a9cc0

Please sign in to comment.