Skip to content

Instantly share code, notes, and snippets.

@freddi301
Last active August 26, 2019 09:59
Show Gist options
  • Save freddi301/608471b4178780e7738462576954f8dc to your computer and use it in GitHub Desktop.
Save freddi301/608471b4178780e7738462576954f8dc to your computer and use it in GitHub Desktop.
React component and hook for rendering promises
export function AsyncNode({ children }: { children: Promise<ReactNode> }) {
return useLatestResolvedValue<ReactNode>(children, () => null) as any;
// as any assertion needed for issue https://github.com/DefinitelyTyped/DefinitelyTyped/issues/20356#issuecomment-336384210
}
export function useLatestResolvedValue<Value>(
promise: Promise<Value>,
initial: () => Value
) {
const [[value], dispatch] = useReducer<
Reducer<
[Value, Array<Promise<Value>>],
| { type: "enqueue"; promise: Promise<Value> }
| { type: "resolved"; promise: Promise<Value>; value: Value }
>
>(latestResolvedPromiseReducer, [initial(), []]);
useEffect(() => {
dispatch({ type: "enqueue", promise });
promise.then(node => dispatch({ type: "resolved", value: node, promise }));
}, [promise]);
return value;
}
function latestResolvedPromiseReducer<Value>(
[display, queue]: [Value, Array<Promise<Value>>],
action:
| { type: "enqueue"; promise: Promise<Value> }
| { type: "resolved"; promise: Promise<Value>; value: Value }
): [Value, Array<Promise<Value>>] {
switch (action.type) {
case "enqueue":
return [display, queue.concat(action.promise)];
case "resolved": {
const index = queue.indexOf(action.promise);
if (index === -1) return [display, queue];
return [action.value, queue.slice(index)];
}
default:
throw new Error();
}
}
// examples
const example1 = <AsyncNode>{Promise.resolve(<h1>hi</h1>)}</AsyncNode>;
const example2 = <RandomDelay />;
async function callApi(text: string) {
await new Promise(resolve => setTimeout(resolve, Math.random() * 3000));
return `response for ${text}`;
}
function RandomDelay() {
const [text, setText] = useState("");
const response = useMemo(() => callApi(text), [text]);
const content = useLatestResolvedValue(response, () => null);
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} /><br/>
<AsyncNode>{response}</AsyncNode><br/>
{content}
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment