From fdeddf0118d515de74b51fdc966c07fad0460261 Mon Sep 17 00:00:00 2001 From: Azri Kahar <42867097+azrikahar@users.noreply.github.com> Date: Fri, 23 Dec 2022 05:31:43 +0800 Subject: [PATCH] Fix use-items loading state when an existing request gets canceled (#16881) * prevent canceled requests from clearing loadingTimeout * rename CancelTokenSource variable * don't set loading to false if there's still loadingTimeout * updated request cancellation to use the abort controller * azri's getItemCount fix * prevent cancelled requests from throwing errors * prevent count request from firing twice * Remove fetchOnInit option Co-authored-by: Brainslug Co-authored-by: rijkvanzanten --- app/src/layouts/calendar/index.ts | 3 +- packages/shared/src/composables/use-items.ts | 101 ++++++++++++------- 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/app/src/layouts/calendar/index.ts b/app/src/layouts/calendar/index.ts index 8c43f686b2..5173acc795 100644 --- a/app/src/layouts/calendar/index.ts +++ b/app/src/layouts/calendar/index.ts @@ -111,8 +111,7 @@ export default defineLayout({ fields: queryFields, filter: filterWithCalendarView, search: search, - }, - false + } ); const events: Ref = computed( diff --git a/packages/shared/src/composables/use-items.ts b/packages/shared/src/composables/use-items.ts index 2db0c01f8c..b32c05f970 100644 --- a/packages/shared/src/composables/use-items.ts +++ b/packages/shared/src/composables/use-items.ts @@ -1,5 +1,5 @@ import { useApi } from './use-system'; -import axios, { CancelTokenSource } from 'axios'; +import axios from 'axios'; import { useCollection } from './use-collection'; import { Item, Query } from '../types'; import { moveInArray } from '../utils'; @@ -34,7 +34,7 @@ type ComputedQuery = { page: Ref | WritableComputedRef; }; -export function useItems(collection: Ref, query: ComputedQuery, fetchOnInit = true): UsableItems { +export function useItems(collection: Ref, query: ComputedQuery): UsableItems { const api = useApi(); const { primaryKeyField } = useCollection(collection); @@ -60,15 +60,15 @@ export function useItems(collection: Ref, query: ComputedQuery, f return Math.ceil(itemCount.value / (unref(limit) ?? 100)); }); - let currentRequest: CancelTokenSource | null = null; + const existingRequests: Record<'items' | 'total' | 'filter', AbortController | null> = { + items: null, + total: null, + filter: null, + }; let loadingTimeout: NodeJS.Timeout | null = null; const fetchItems = throttle(getItems, 500); - if (fetchOnInit) { - fetchItems(); - } - watch( [collection, limit, sort, search, filter, fields, page], async (after, before) => { @@ -79,6 +79,10 @@ export function useItems(collection: Ref, query: ComputedQuery, f if (!newCollection || !query) return; + if (newCollection !== oldCollection) { + reset(); + } + if ( !isEqual(newFilter, oldFilter) || !isEqual(newSort, oldSort) || @@ -90,14 +94,10 @@ export function useItems(collection: Ref, query: ComputedQuery, f } } - if (!isEqual(newFilter, oldFilter) || newSearch !== oldSearch) { + if (newCollection !== oldCollection || !isEqual(newFilter, oldFilter) || newSearch !== oldSearch) { getItemCount(); } - if (newCollection !== oldCollection) { - reset(); - } - fetchItems(); }, { deep: true, immediate: true } @@ -119,8 +119,10 @@ export function useItems(collection: Ref, query: ComputedQuery, f async function getItems() { if (!endpoint.value) return; - currentRequest?.cancel(); - currentRequest = null; + let isCurrentRequestCanceled = false; + + if (existingRequests.items) existingRequests.items.abort(); + existingRequests.items = new AbortController(); error.value = null; @@ -152,8 +154,6 @@ export function useItems(collection: Ref, query: ComputedQuery, f fieldsToFetch = fieldsToFetch.filter((field) => field.startsWith('$') === false); try { - currentRequest = axios.CancelToken.source(); - const response = await api.get(endpoint.value, { params: { limit: unref(limit), @@ -164,10 +164,11 @@ export function useItems(collection: Ref, query: ComputedQuery, f search: unref(search), filter: unref(filter), }, - cancelToken: currentRequest.token, + signal: existingRequests.items.signal, }); let fetchedItems = response.data.data; + existingRequests.items = null; /** * @NOTE @@ -192,16 +193,18 @@ export function useItems(collection: Ref, query: ComputedQuery, f page.value = 1; } } catch (err: any) { - if (!axios.isCancel(err)) { + if (axios.isCancel(err)) { + isCurrentRequestCanceled = true; + } else { error.value = err; } } finally { - if (loadingTimeout) { + if (loadingTimeout && !isCurrentRequestCanceled) { clearTimeout(loadingTimeout); loadingTimeout = null; } - loading.value = false; + if (!loadingTimeout) loading.value = false; } } @@ -227,34 +230,56 @@ export function useItems(collection: Ref, query: ComputedQuery, f async function getTotalCount() { if (!endpoint.value) return; - const response = await api.get(endpoint.value, { - params: { - aggregate: { - count: '*', + try { + if (existingRequests.total) existingRequests.total.abort(); + existingRequests.total = new AbortController(); + + const response = await api.get(endpoint.value, { + params: { + aggregate: { + count: '*', + }, }, - }, - }); + signal: existingRequests.total.signal, + }); - const count = Number(response.data.data[0].count); + const count = Number(response.data.data[0].count); + existingRequests.total = null; - totalCount.value = count; + totalCount.value = count; + } catch (err: any) { + if (!axios.isCancel(err)) { + throw err; + } + } } async function getItemCount() { if (!endpoint.value) return; - const response = await api.get(endpoint.value, { - params: { - filter: unref(filter), - search: unref(search), - aggregate: { - count: '*', + try { + if (existingRequests.filter) existingRequests.filter.abort(); + existingRequests.filter = new AbortController(); + + const response = await api.get(endpoint.value, { + params: { + filter: unref(filter), + search: unref(search), + aggregate: { + count: '*', + }, }, - }, - }); + signal: existingRequests.filter.signal, + }); - const count = Number(response.data.data[0].count); + const count = Number(response.data.data[0].count); + existingRequests.filter = null; - itemCount.value = count; + itemCount.value = count; + } catch (err: any) { + if (!axios.isCancel(err)) { + throw err; + } + } } }