From a6b6d085a41451abba87babd2c700b3ca29510dd Mon Sep 17 00:00:00 2001 From: webbrain-one <295484252+webbrain-one@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:42:24 +0300 Subject: [PATCH] fix: prevent suspense on load more fetches Address Issue #104. Prevents suspense from triggering when fetching additional data in infinite scroll scenarios, eliminating unwanted re-renders. Updates useAsyncResource to handle incremental data fetching and adds an option to disable suspense when needed. --- src/index.ts | 2 ++ src/useAsyncResource.ts | 32 +++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index b073550..c1ed7b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import * as fileResource from './fileResource'; import { resourceCache } from './cache'; import { initializeDataReader as preloadResource } from './dataReaderInitializer'; import AsyncResourceContent from './AsyncResourceContent'; +import { AsyncResourceOptions } from './useAsyncResource'; export { useAsyncResource, @@ -12,4 +13,5 @@ export { fileResource, resourceCache, AsyncResourceContent, + AsyncResourceOptions, } diff --git a/src/useAsyncResource.ts b/src/useAsyncResource.ts index b53674e..73c4dab 100644 --- a/src/useAsyncResource.ts +++ b/src/useAsyncResource.ts @@ -8,6 +8,10 @@ import { } from './types'; import { initializeDataReader } from './dataReaderInitializer'; +export interface AsyncResourceOptions { + suspend?: boolean; +} + /** * Lazy initializer. * The only param passed is the api function that will be wrapped. @@ -20,6 +24,7 @@ import { initializeDataReader } from './dataReaderInitializer'; */ export function useAsyncResource( apiFunction: ApiFn, + options?: AsyncResourceOptions ): [LazyDataOrModifiedFn, UpdaterFn]; /** @@ -37,6 +42,7 @@ export function useAsyncResource( export function useAsyncResource( apiFunction: ApiFn, eagerLoading: never[], // the type of an empty array `[]` is `never[]` + options?: AsyncResourceOptions ): [DataOrModifiedFn, UpdaterFn]; /** @@ -51,14 +57,21 @@ export function useAsyncResource( */ export function useAsyncResource( apiFunction: ApiFn, - ...parameters: ArgTypes + ...paramsAndOptions: [...ArgTypes, AsyncResourceOptions?] ): [DataOrModifiedFn, UpdaterFn]; // implementation that covers the above overloads export function useAsyncResource( apiFunction: ApiFn | ApiFn, - ...parameters: ArgTypes + ...args: any[] ) { + const lastArg = args[args.length - 1]; + const options = (typeof lastArg === 'object' && lastArg !== null && 'suspend' in lastArg) + ? (lastArg as AsyncResourceOptions) + : {}; + const parameters = options.suspend !== undefined ? args.slice(0, -1) : args; + const shouldSuspend = options.suspend !== false; + // keep the data reader inside a mutable object ref // always initialize with a lazy data reader, as it can be overwritten by the useMemo immediately const dataReaderObj = useRef | LazyDataOrModifiedFn>(() => undefined); @@ -96,5 +109,18 @@ export function useAsyncResource( forceRender(ct => 1 - ct); }, [apiFunction]); - return [dataReaderObj.current, updaterFn]; + if (shouldSuspend) { + return [dataReaderObj.current, updaterFn]; + } + + const safeDataReader: LazyDataOrModifiedFn = (modifier) => { + try { + return dataReaderObj.current(modifier as any); + } catch (e) { + if (e && typeof (e as any).then === 'function') return undefined; + throw e; + } + }; + + return [safeDataReader, updaterFn]; }