|
// Implementation of the hook. Happens to be in typescript, but doesn't use typescript enums. |
|
|
|
import * as React from 'react'; |
|
import usePromiseLib from 'react-use-promise'; |
|
import { Option } from 'safe-types'; |
|
|
|
type State = 'pending' | 'resolved' | 'rejected'; |
|
|
|
export class UsePromiseState<Result> { |
|
_result: Result | null; |
|
_error: unknown; |
|
_state: State; |
|
constructor(result: Result | null, error: any, state: State) { |
|
this._result = result; |
|
this._error = error; |
|
this._state = state; |
|
} |
|
|
|
// Get the latest value... ignoring the state |
|
value(): Option<Result> { |
|
return Option.of(this._result); |
|
} |
|
|
|
// Get the latest error... ignoring the state |
|
error(): Option<unknown> { |
|
return Option.of(this._error); |
|
} |
|
|
|
// Get the value if in a success state |
|
success(): Option<Result> { |
|
return this.value().filter((x) => this._state === 'resolved'); |
|
} |
|
|
|
// Get the error if in an failure state |
|
failure(): Option<unknown> { |
|
return this.error().filter((x: unknown) => this._state === 'rejected'); |
|
} |
|
|
|
// Get if we're current in a loading state |
|
loading(): boolean { |
|
return this._state === 'pending'; |
|
} |
|
|
|
// Get either Some(true) or None, for loading vs not loading |
|
// Maybe this isn't needed because of match |
|
loadingOption(): Option<true> { |
|
if (this.loading()) { |
|
return Option.Some(true); |
|
} else { |
|
return Option.None(); |
|
} |
|
} |
|
|
|
match<Out>( |
|
matcher: Partial<{ |
|
value: (value: Result) => Out; |
|
error: (error: unknown) => Out; |
|
success: (value: Result) => Out; |
|
failure: (value: unknown) => Out; |
|
loading: () => Out; |
|
default: () => Out; |
|
}>, |
|
): Out { |
|
for (const key of Object.keys(matcher)) { |
|
if (key === 'value' && matcher.value) { |
|
const v = this.value(); |
|
if (v.is_some()) { |
|
return matcher.value(v.unwrap()); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
if (key === 'error' && matcher.error) { |
|
const v = this.error(); |
|
if (v.is_some()) { |
|
return matcher.error(v.unwrap()); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
if (key === 'success' && matcher.success) { |
|
const v = this.success(); |
|
if (v.is_some()) { |
|
return matcher.success(v.unwrap()); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
if (key === 'failure' && matcher.failure) { |
|
const v = this.failure(); |
|
if (v.is_some()) { |
|
return matcher.failure(v.unwrap()); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
if (key === 'loading' && matcher.loading) { |
|
if (this.loading()) { |
|
return matcher.loading(); |
|
} else { |
|
continue; |
|
} |
|
} |
|
|
|
if (key !== 'default') { |
|
throw new Error( |
|
`Unexpected matcher key "${key}". See src/utils/usePromise for the expected API`, |
|
); |
|
} |
|
} |
|
|
|
if (matcher.default) { |
|
return matcher.default(); |
|
} |
|
|
|
throw new Error( |
|
`No cases matched. Define a 'default' case as a fallback if needed.`, |
|
); |
|
} |
|
} |
|
|
|
function usePromise<Result = any>( |
|
promise: Promise<Result> | (() => Promise<Result>), |
|
deps?: Array<any>, |
|
): UsePromiseState<Result> { |
|
const [result, error, state] = usePromiseLib<Result>(promise, deps); |
|
|
|
let lastResult = React.useRef<Result | null>(null); |
|
let lastError = React.useRef<any>(null); |
|
|
|
const service = React.useMemo(() => { |
|
if (result != null) { |
|
lastResult.current = result; |
|
} |
|
if (error != null) { |
|
lastError.current = error; |
|
} |
|
return new UsePromiseState(lastResult.current, lastError.current, state); |
|
}, [result, error, state]); |
|
|
|
return service; |
|
} |
|
|
|
export default usePromise; |