Created
October 29, 2019 19:37
-
-
Save pie6k/e4f8464d471929cd4f81281655b704d8 to your computer and use it in GitHub Desktop.
useSuspend without waterfall
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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