Reduce occurence of "A component was suspended by an uncached promise"

This commit is contained in:
Stan Wohlwend 2024-06-16 15:55:37 +02:00
parent 5cf167b5c7
commit ace8497ca6
2 changed files with 28 additions and 6 deletions

View File

@ -1,4 +1,5 @@
import { StackAssertionError, captureError } from "./errors";
import { DependenciesMap } from "./maps";
import { Result } from "./results";
import { generateUuid } from "./uuids";
@ -38,28 +39,45 @@ export function createPromise<T>(callback: (resolve: Resolve<T>, reject: Reject)
} as any);
}
const resolvedCache = new DependenciesMap<[unknown], ReactPromise<unknown>>();
/**
* Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook.
* Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook, and caches
* the value so that invoking `resolved` twice returns the same promise.
*/
export function resolved<T>(value: T): ReactPromise<T> {
return Object.assign(Promise.resolve(value), {
if (resolvedCache.has([value])) {
return resolvedCache.get([value]) as ReactPromise<T>;
}
const res = Object.assign(Promise.resolve(value), {
status: "fulfilled",
value,
} as const);
resolvedCache.set([value], res);
return res;
}
const rejectedCache = new DependenciesMap<[unknown], ReactPromise<unknown>>();
/**
* Like Promise.resolve(...), but also adds the status and value properties for use with React's `use` hook.
* Like Promise.reject(...), but also adds the status and value properties for use with React's `use` hook, and caches
* the value so that invoking `rejected` twice returns the same promise.
*/
export function rejected<T>(reason: unknown): ReactPromise<T> {
return Object.assign(Promise.reject(reason), {
if (rejectedCache.has([reason])) {
return rejectedCache.get([reason]) as ReactPromise<T>;
}
const res = Object.assign(Promise.reject(reason), {
status: "rejected",
reason: reason,
} as const);
rejectedCache.set([reason], res);
return res;
}
const neverResolvePromise = pending(new Promise<never>(() => {}));
export function neverResolve(): ReactPromise<never> {
return pending(new Promise<never>(() => {}));
return neverResolvePromise;
}
export function pending<T>(promise: Promise<T>, options: { disableErrorWrapping?: boolean } = {}): ReactPromise<T> {

View File

@ -198,7 +198,11 @@ function useAsyncCache<D extends any[], T>(cache: AsyncCache<D, T>, dependencies
// note: we must use React.useSyncExternalStore instead of importing the function directly, as it will otherwise
// throw an error ("can't import useSyncExternalStore from the server")
const value = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const value = React.useSyncExternalStore(
subscribe,
getSnapshot,
() => throwErr(new Error("getServerSnapshot should never be called in useAsyncCache because we restrict to CSR earlier"))
);
if (value === loadingSentinel) {
return use(cache.getOrWait(dependencies, "read-write"));