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 <tim@brainslug.nl>
Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
Azri Kahar
2022-12-23 05:31:43 +08:00
committed by GitHub
parent 602f5db4f7
commit fdeddf0118
2 changed files with 64 additions and 40 deletions

View File

@@ -111,8 +111,7 @@ export default defineLayout<LayoutOptions>({
fields: queryFields,
filter: filterWithCalendarView,
search: search,
},
false
}
);
const events: Ref<EventInput> = computed(

View File

@@ -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<Query['page']> | WritableComputedRef<Query['page']>;
};
export function useItems(collection: Ref<string | null>, query: ComputedQuery, fetchOnInit = true): UsableItems {
export function useItems(collection: Ref<string | null>, query: ComputedQuery): UsableItems {
const api = useApi();
const { primaryKeyField } = useCollection(collection);
@@ -60,15 +60,15 @@ export function useItems(collection: Ref<string | null>, 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<string | null>, 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<string | null>, 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<string | null>, 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<string | null>, query: ComputedQuery, f
fieldsToFetch = fieldsToFetch.filter((field) => field.startsWith('$') === false);
try {
currentRequest = axios.CancelToken.source();
const response = await api.get<any>(endpoint.value, {
params: {
limit: unref(limit),
@@ -164,10 +164,11 @@ export function useItems(collection: Ref<string | null>, 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<string | null>, 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<string | null>, query: ComputedQuery, f
async function getTotalCount() {
if (!endpoint.value) return;
const response = await api.get<any>(endpoint.value, {
params: {
aggregate: {
count: '*',
try {
if (existingRequests.total) existingRequests.total.abort();
existingRequests.total = new AbortController();
const response = await api.get<any>(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<any>(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<any>(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;
}
}
}
}