mirror of
https://github.com/Steffo99/festa.git
synced 2024-12-23 15:14:23 +00:00
145 lines
4.8 KiB
TypeScript
145 lines
4.8 KiB
TypeScript
import { memo, Reducer, useCallback, useEffect, useMemo, useReducer } from "react";
|
|
|
|
/**
|
|
* The possible states of a {@link Promise}, plus an additional one that represents that it has not been started yet.
|
|
*
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
*/
|
|
export enum UsePromiseStatus {
|
|
READY,
|
|
PENDING,
|
|
REJECTED,
|
|
FULFILLED,
|
|
}
|
|
|
|
/**
|
|
* Action to start running the {@link Promise} contained in the {@link usePromise} hook, putting it in the {@link UsePromiseStatus.PENDING} state.
|
|
*/
|
|
type UsePromiseActionRun = { type: "start" }
|
|
|
|
/**
|
|
* Action to fulfill the {@link Promise} contained in the {@link usePromise} hook, putting it in the {@link UsePromiseStatus.FULFILLED} state.
|
|
*/
|
|
type UsePromiseActionFulfill<D> = { type: "fulfill", result: D }
|
|
|
|
/**
|
|
* Action to reject the {@link Promise} contained in the {@link usePromise} hook, putting it in the {@link UsePromiseStatus.REJECTED} state.
|
|
*/
|
|
type UsePromiseActionReject<E = Error> = { type: "reject", error: E }
|
|
|
|
/**
|
|
* Actions that can be performed on the {@link useReducer} hook used inside {@link usePromise}.
|
|
*/
|
|
type UsePromiseAction<D, E = Error> = UsePromiseActionRun | UsePromiseActionFulfill<D> | UsePromiseActionReject<E>;
|
|
|
|
/**
|
|
* The internal state of the {@link useReducer} hook used inside {@link usePromise}.
|
|
*/
|
|
type UsePromiseState<D, E = Error> = {
|
|
status: UsePromiseStatus,
|
|
result: D | undefined,
|
|
error: E | undefined,
|
|
}
|
|
|
|
/**
|
|
* The initial {@link UsePromiseState} of the {@link usePromise} hook.
|
|
*/
|
|
const initialUsePromise: UsePromiseState<any, any> = {
|
|
status: UsePromiseStatus.READY,
|
|
result: undefined,
|
|
error: undefined,
|
|
}
|
|
|
|
/**
|
|
* The reducer used by {@link usePromise}.
|
|
*/
|
|
function reducerUsePromise<D, E = Error>(prev: UsePromiseState<D, E>, action: UsePromiseAction<D, E>): UsePromiseState<D, E> {
|
|
switch (action.type) {
|
|
case "start":
|
|
return { ...prev, status: UsePromiseStatus.PENDING }
|
|
case "fulfill":
|
|
return { status: UsePromiseStatus.FULFILLED, result: action.result, error: undefined }
|
|
case "reject":
|
|
return { status: UsePromiseStatus.REJECTED, error: action.error, result: undefined }
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Async function that can be ran using {@link usePromise}.
|
|
*/
|
|
type UsePromiseFunction<D, P> = (params: P) => Promise<D>
|
|
|
|
/**
|
|
* Values returned by the {@link usePromise} hook.
|
|
*/
|
|
export type UsePromise<D, P, E = Error> = UsePromiseState<D, E> & { run: (params: P) => Promise<void> }
|
|
|
|
/**
|
|
* Hook executing an asyncronous function in a way that can be handled by React components.
|
|
*/
|
|
export function usePromise<D, P, E = Error>(func: UsePromiseFunction<D, P>): UsePromise<D, P, E> {
|
|
const [state, dispatch] = useReducer<Reducer<UsePromiseState<D, E>, UsePromiseAction<D, E>>>(reducerUsePromise, initialUsePromise)
|
|
|
|
const run = useCallback(
|
|
async (params: P) => {
|
|
dispatch({ type: "start" })
|
|
|
|
try {
|
|
var result = await func(params)
|
|
}
|
|
catch (error) {
|
|
dispatch({ type: "reject", error: error as E })
|
|
return
|
|
}
|
|
|
|
dispatch({ type: "fulfill", result })
|
|
return
|
|
},
|
|
[func]
|
|
)
|
|
|
|
return { ...state, run }
|
|
}
|
|
|
|
|
|
export type PromiseMultiplexerReadyParams<D, P, E = Error> = {
|
|
run: UsePromise<D, P, E>["run"],
|
|
}
|
|
|
|
export type PromiseMultiplexerPendingParams<D, P, E = Error> = {
|
|
|
|
}
|
|
|
|
export type PromiseMultiplexerFulfilledParams<D, P, E = Error> = {
|
|
run: UsePromise<D, P, E>["run"],
|
|
result: D,
|
|
}
|
|
|
|
export type PromiseMultiplexerRejectedParams<D, P, E = Error> = {
|
|
run: UsePromise<D, P, E>["run"],
|
|
error: E,
|
|
}
|
|
|
|
|
|
export type PromiseMultiplexerConfig<D, P, E = Error> = {
|
|
hook: UsePromise<D, P, E>,
|
|
ready: (params: PromiseMultiplexerReadyParams<D, P, E>) => JSX.Element,
|
|
pending: (params: PromiseMultiplexerPendingParams<D, P, E>) => JSX.Element,
|
|
fulfilled: (params: PromiseMultiplexerFulfilledParams<D, P, E>) => JSX.Element,
|
|
rejected: (error: PromiseMultiplexerRejectedParams<D, P, E>) => JSX.Element,
|
|
}
|
|
|
|
/**
|
|
* Function which selects and memoizes an output based on the {@link UsePromiseStatus} of a {@link usePromise} hook.
|
|
*
|
|
* It would be nice for it to be a component, but TSX does not support that, since it's a generic function, and that would make all its types `unknown`.
|
|
*/
|
|
export function promiseMultiplexer<D, P, E = Error>(config: PromiseMultiplexerConfig<D, P, E>): JSX.Element {
|
|
switch (config.hook.status) {
|
|
case UsePromiseStatus.READY: return config.ready({ run: config.hook.run })
|
|
case UsePromiseStatus.PENDING: return config.pending({})
|
|
case UsePromiseStatus.FULFILLED: return config.fulfilled({ run: config.hook.run, result: config.hook.result! })
|
|
case UsePromiseStatus.REJECTED: return config.rejected({ run: config.hook.run, error: config.hook.error! })
|
|
}
|
|
}
|