Skip to content

Instantly share code, notes, and snippets.

@pie6k
Created October 29, 2019 19:37
Show Gist options
  • Save pie6k/e4f8464d471929cd4f81281655b704d8 to your computer and use it in GitHub Desktop.
Save pie6k/e4f8464d471929cd4f81281655b704d8 to your computer and use it in GitHub Desktop.
useSuspend without waterfall
import { useRef } from 'react';
const noValueSymbol = Symbol();
function didBitsChange(oldBits: any[], newBits: any) {
return oldBits.some((oldBit, index) => !Object.is(oldBit, newBits[index]));
}
export function useSuspend<T>(
fetcher: () => Promise<T>,
refetchBits: any[] = [],
) {
const promiseRef = useRef<Promise<T> | null>(null);
const valueRef = useRef<T | symbol>(noValueSymbol);
const promiseErrorRef = useRef<any>(noValueSymbol);
const oldRefetchBitsRef = useRef(refetchBits);
if (
promiseRef.current !== null &&
didBitsChange(oldRefetchBitsRef.current, refetchBits)
) {
promiseRef.current = null;
valueRef.current = noValueSymbol;
promiseErrorRef.current = noValueSymbol;
}
try {
if (!promiseRef.current) {
const fetcherPromise = fetcher();
promiseRef.current = fetcherPromise;
fetcherPromise
.then((result) => {
valueRef.current = result;
})
.catch((error) => {
promiseErrorRef.current = error;
});
}
} catch (promiseCreationError) {
throw promiseCreationError;
}
function read() {
if (valueRef.current !== noValueSymbol) {
return valueRef.current as T;
}
if (promiseErrorRef.current !== noValueSymbol) {
throw promiseErrorRef.current;
}
if (promiseRef.current !== null) {
throw promiseRef.current;
}
throw new Error('Unaable to suspend useSuspend read');
}
return [read] as const;
}
function fetchUser(id: string) {
return new Promise<{ name: string }>((resolve) => {
setTimeout(() => resolve({ name: 'Bob' }), 1000);
});
}
// fetchUser is instantly fired and starts to resolve, but it'll not be suspended untill we call getNumber().
// it allows us to use few suspended values and have all of them start to resolve before we suspend the component when first of those values is used
// it's also possible that getNumber will never be called during render, but promise will still resolve for next renders and suspense won't be used at all
const [getNumber] = useSuspend(() => fetchUser('id-of-user'), ['id-of-user']);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment