import type { PropsWithChildren, ReactElement } from "react"; import React, { Fragment, useEffect, useMemo, useRef, useState } from "react"; import { Else } from "./Else"; import { Fallback } from "./Fallback"; import { useSingleton } from "./hooks"; import { isThenable } from "./isThenable"; import { Then } from "./Then"; import type { AsyncSupportProps, CancellablePromise, ExtendablePromise } from "./types"; import { _memo, _shallowFn, createCancellablePromise } from "./utils"; /** * Props for IfAsync */ interface Props extends AsyncSupportProps, PropsWithChildren<{ promise: ExtendablePromise }> {} /** * Is included in the `` component, rendered when the condition prop of `` is a Promise; * Renders the Fallback component, if contains any, until provided promise is fulfilled; * Renders `` when promise is fulfilled, `` when rejected */ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type function IfAsyncFn({ promise, keepAlive = false, children }: Props) { const [isResolved, setIsResolved] = useState(null); const [returnValue, setReturnValue] = useState(null); // Make promise cancellable const cancellablePromise = useMemo((): CancellablePromise => createCancellablePromise(promise), [promise]); const history = useRef([]); // Keep history of promises // Handle unmount useEffect(() => { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type return () => { if (!keepAlive) { cancellablePromise.cancel(); } }; }, [cancellablePromise, cancellablePromise.promise, keepAlive]); // Await promise useSingleton(async () => { setIsResolved(null); setReturnValue(null); try { const data = await cancellablePromise.promise; setReturnValue(data); setIsResolved(true); history.current.push(cancellablePromise); } catch (error) { setReturnValue(error as any); setIsResolved(false); history.current.push(cancellablePromise); } }, [cancellablePromise.promise]); if (!children || !isThenable(promise)) { return null; } if (isResolved === null) { // Promise is pending const hasFallback = (React.Children.toArray(children) as ReactElement[]).find((c) => c.type === Fallback); return {hasFallback || null}; } if (!isResolved) { // Promise is fulfilled and rejected const hasElse = (React.Children.toArray(children) as ReactElement[]).find((c) => c.type === Else); if (!hasElse) return {null}; // Inject caught error let elseElement = hasElse; if (typeof hasElse.props.children === "function") { elseElement = { ...hasElse, props: { ...hasElse.props, // eslint-disable-next-line @typescript-eslint/explicit-function-return-type children: () => hasElse.props.children(returnValue, history.current, cancellablePromise.promise) } }; } return {elseElement}; } // Promise is fulfilled and resolved const hasThen = (React.Children.toArray(children) as ReactElement[]).find((c) => c.type === Then); if (!hasThen) return {null}; // Inject promise return value let thenElement = hasThen; if (typeof hasThen.props.children === "function") { thenElement = { ...hasThen, props: { ...hasThen.props, // eslint-disable-next-line @typescript-eslint/explicit-function-return-type children: () => hasThen.props.children(returnValue, history.current, cancellablePromise.promise) } }; } return {thenElement}; } const IfAsync = _memo(IfAsyncFn, _shallowFn); export { IfAsync };