Created
February 22, 2019 23:12
-
-
Save dfee/0686746c4b37ad45ba88b20a66a60432 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 { useState, useEffect } from "react"; | |
export type AsyncState<T> = | |
| { loading: false; error?: undefined; value?: undefined } | |
| { loading: true; error?: undefined; value?: undefined } | |
| { loading: false; error: Error; value?: undefined } | |
| { loading: false; error?: undefined; value: T }; | |
export type AsyncUtils<C extends {}, A extends any[]> = { | |
setArgs: (args: A | undefined) => void; | |
setContext: (optiosn: C | undefined) => void; | |
}; | |
const noop = () => {}; | |
/** | |
* Executes an async function (a function that returns a promise), and | |
* returns the result (value or error). | |
* | |
* @param fn the async function to execute | |
* @param options a bag of options controlling the async execution | |
* | |
* @param options.initialContext context necessary for initial execution | |
* @param options.initialArgs args for initial execution (initial execution is skipped if undefined) | |
* @param options.onSuccess callback receiving the context and value, executed upon `then` | |
* @param options.onError callback receiving the context and reason, executed upon `catch` | |
* @param options.onFinally callback receiving the context, executed upon `finally` | |
* | |
* Returns an array that is a state object and a utils object `[state, utils]` | |
* @param state.loading boolean indicating whether an async action is in flight | |
* @param state.error the error or reason thrown during async execution | |
* @param state.value the value returned from async execution | |
* @param utils.setArguments set the argumetns for async execution (if the value is undefined, no execution occurs) | |
* @param utils.setContext set any context necessary for async execution | |
*/ | |
export const useAsync = < | |
C extends {} = any, | |
A extends any[] = any, | |
T extends {} = any | |
>( | |
fn: (...args: A) => Promise<T>, | |
options?: { | |
initialContext?: C; | |
initialArgs?: A; | |
onSuccess?: ({ context, value }: { context?: C; value: T }) => void; | |
onError?: ({ context, reason }: { context?: C; reason: any }) => void; | |
onFinally?: ({ context }: { context?: C }) => void; | |
}, | |
): [AsyncState<T>, AsyncUtils<C, A>] => { | |
const { | |
initialArgs = undefined, | |
initialContext = undefined, | |
onSuccess = noop, | |
onError = noop, | |
onFinally = noop, | |
} = { ...options }; | |
let mounted = true; | |
const [state, setState] = useState<AsyncState<T>>({ loading: false }); | |
const [args, setArgs] = useState<A | undefined>(initialArgs); | |
const [context, setContext] = useState<C | undefined>(initialContext); | |
const utils = { setArgs, setContext }; | |
useEffect(() => { | |
if (args === undefined) { | |
setState({ loading: false }); | |
} else { | |
setState({ loading: true }); | |
fn(...args) | |
.then(value => { | |
if (mounted) { | |
setState({ loading: false, value }); | |
} | |
onSuccess({ context, value }); | |
}) | |
.catch(reason => { | |
if (mounted) { | |
setState({ loading: false, error: reason }); | |
} | |
onError({ context, reason }); | |
}) | |
.finally(() => { | |
onFinally({ context }); | |
}); | |
} | |
return () => { | |
mounted = false; | |
}; | |
}, [args, context]); | |
return [state, utils]; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment