From 21174dc04ab392cca5ae00f45f2b75a45f37840f Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Thu, 8 Jun 2023 13:40:50 +0200 Subject: [PATCH] Add the option to ignore SWR stale data (#356) --- src/lib/RequestManager.ts | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/src/lib/RequestManager.ts b/src/lib/RequestManager.ts index 40222ffc..ce72f381 100644 --- a/src/lib/RequestManager.ts +++ b/src/lib/RequestManager.ts @@ -7,7 +7,7 @@ */ import { AxiosInstance, AxiosRequestConfig } from 'axios'; -import useSWR, { SWRConfiguration, SWRResponse } from 'swr'; +import useSWR, { Middleware, SWRConfiguration, SWRResponse } from 'swr'; import useSWRInfinite, { SWRInfiniteConfiguration, SWRInfiniteResponse } from 'swr/infinite'; import { BackupValidationResult, @@ -47,6 +47,7 @@ type RequestOption = { doOnlineFetch?: boolean }; type CustomSWROptions = { skipRequest?: boolean; getEndpoint?: (index: number, previousData: Data | null) => string | null; + disableCache?: boolean; }; type SWROptions = SWRConfiguration & CustomSWROptions; @@ -63,6 +64,30 @@ export type AbortableSWRInfiniteResponse = SWRInfiniteR AbortableRequest & SWRInfiniteResponseLoadInfo; +const isLoadingMore = (swrResult: SWRInfiniteResponse): boolean => { + const isNextPageMissing = !!swrResult.data && typeof swrResult.data[swrResult.size - 1] === 'undefined'; + // SWR "isLoading" state is only updated for the first load, for every subsequent load it's "false" + return !swrResult.isLoading && swrResult.size > 0 && isNextPageMissing; +}; + +const disableSwrInfiniteCache: Middleware = (useSWRNext) => (key, fetcher, config) => { + const swr = useSWRNext(key, fetcher, config) as unknown as SWRInfiniteResponse; + const { size, data, isLoading, isValidating } = swr; + const isActuallyValidating = !isLoading && !isLoadingMore(swr) && isValidating; + return { + ...swr, + isLoading: isActuallyValidating ? true : isLoading, + data: isActuallyValidating ? undefined : data, + size: isActuallyValidating ? 1 : size, + } as SWRResponse; +}; + +const disableSwrCache: Middleware = (useSWRNext) => (key, fetcher, config) => { + const swr = useSWRNext(key, fetcher, config); + const { data, isLoading, isValidating } = swr; + return { ...swr, isLoading: isValidating ? true : isLoading, data: isValidating ? undefined : data }; +}; + // the following endpoints have not been implemented: // - PUT /api/v1/manga/{mangaId}/chapter/{chapterIndex} - modify chapter # PATCH endpoint used instead // - POST /api/v1/backup/import - import backup # "import backup file" endpoint used instead @@ -129,11 +154,12 @@ export class RequestManager { swrOptions?: OptionsSWR; } = {}, ): SWRResponse { - const { skipRequest, ...swrConfig } = swrOptions ?? {}; + const { skipRequest, disableCache, ...swrConfig } = swrOptions ?? {}; // in case "null" gets passed as the url, SWR won't do the request return useSWR(skipRequest ? null : url, { fetcher: (path: string) => this.restClient.fetcher(path, { data, httpMethod, config: axiosOptions }), + use: disableCache ? [disableSwrCache] : undefined, ...swrConfig, }); } @@ -151,7 +177,7 @@ export class RequestManager { swrOptions, }: { data?: any; axiosOptions?: AxiosRequestConfig; swrOptions?: OptionsSWR } = {}, ): SWRInfiniteResponse & SWRInfiniteResponseLoadInfo { - const { skipRequest, ...swrConfig } = swrOptions ?? {}; + const { skipRequest, disableCache, ...swrConfig } = swrOptions ?? {}; // useSWRInfinite will (by default) revalidate the first page, to check if the other pages have to be revalidated as well const swrResult = useSWRInfinite( @@ -161,17 +187,15 @@ export class RequestManager { }, { fetcher: (path: string) => this.restClient.fetcher(path, { httpMethod, data, config: axiosOptions }), + use: disableCache ? [disableSwrInfiniteCache] : undefined, ...swrConfig, }, ); - const isNextPageMissing = !!swrResult.data && typeof swrResult.data[swrResult.size - 1] === 'undefined'; - const isLoadingMore = swrResult.size > 0 && isNextPageMissing; const customSwrResult = { ...swrResult, isInitialLoad: swrResult.isLoading, - // SWR "isLoading" state is only updated for the first load, for every subsequent load it's "false" - isLoadMore: !swrResult.isLoading && isLoadingMore, + isLoadMore: isLoadingMore(swrResult), }; customSwrResult.isLoading = customSwrResult.isInitialLoad || customSwrResult.isLoadMore;