/* A useMemo that ensures the result is not recalculated if provided dependencies did not change. https://react.dev/reference/react/useMemo#caveats */ export const useMemoStable = (fn, deps) => { const ref = useRef(null) if (!ref.current || !deps || !shallowEqualArrays(ref.current.deps, deps)) { ref.current = { value: fn(), deps: deps || [], } } return ref.current.value } const defaultEnhance = ({ status, value, reason }) => ({ status, value, reason, loading: status === 'pending', ok: status === 'fulfilled', data: value, error: reason, }) export const useAsyncMemo = (asyncFn, deps, enhance = defaultEnhance) => { const curr = useMemoStable(() => ({ state: enhance({ status: 'pending' }), }), deps) const [, setDummyState] = useState() useLayoutEffect(() => { curr.aborter = new AbortController() return () => curr.aborter.abort() }, [curr]) useEffect(() => { const { signal } = curr.aborter Promise.allSettled([asyncFn({ signal })]) .then(([settled]) => { if (!signal.aborted) { curr.state = enhance(settled) setDummyState([]) } }) }, [curr]) return curr.state }