|
import { ReactNodeLike } from "prop-types"; |
|
import { MouseEvent, useState } from "react"; |
|
|
|
type LoadingState = `default` | `loading` | `complete` | `error`; |
|
|
|
type LoadingButtonProps = { |
|
render: (state: LoadingState) => ReactNodeLike; |
|
className?: string | undefined; |
|
loadingClassName?: string | undefined; |
|
completeClassName?: string | undefined; |
|
errorClassName?: string | undefined; |
|
onClick?: (event: MouseEvent<HTMLButtonElement>) => Promise<void>; |
|
onError?: (reason: unknown) => void; |
|
disabled?: boolean; |
|
disableOnSuccess?: boolean; |
|
disableOnError?: boolean; |
|
}; |
|
|
|
export default function LoadingButton( |
|
{ |
|
render, |
|
className, |
|
loadingClassName, |
|
completeClassName, |
|
errorClassName, |
|
onClick, |
|
onError, |
|
disabled, |
|
disableOnSuccess, |
|
disableOnError, |
|
}: LoadingButtonProps, |
|
): JSX.Element { |
|
const [state, setState] = useState<LoadingState>(`default`); |
|
const getStyles = (s: LoadingState): string | undefined => { |
|
switch (s) { |
|
case `loading`: |
|
return loadingClassName; |
|
case `complete`: |
|
return completeClassName; |
|
case `error`: |
|
return errorClassName; |
|
default: |
|
return className; |
|
} |
|
}; |
|
const isDisabled = state === `loading` |
|
|| (state === `complete` && disableOnSuccess) |
|
|| (state === `error` && disableOnError) |
|
|| disabled; |
|
|
|
return ( |
|
<button |
|
type="button" |
|
className={getStyles(state)} |
|
disabled={isDisabled} |
|
onClick={async (e) => { |
|
setState(`loading`); |
|
try { |
|
await onClick(e); |
|
setState(`complete`); |
|
} catch (err) { |
|
setState(`error`); |
|
onError(err); |
|
} |
|
}} |
|
> |
|
{render(state)} |
|
</button> |
|
); |
|
} |
|
|
|
LoadingButton.defaultProps = { |
|
className: undefined, |
|
loadingClassName: undefined, |
|
completeClassName: undefined, |
|
errorClassName: undefined, |
|
onClick: () => {}, |
|
onError: () => {}, |
|
disabled: false, |
|
disableOnSuccess: true, |
|
disableOnError: true, |
|
}; |
|
|
|
export function LoadingButtonUsage(): JSX.Element { |
|
return ( |
|
<LoadingButton |
|
render={(state) => { |
|
if (state === `loading`) return `Processing...`; |
|
if (state === `complete`) return `Complete!`; |
|
if (state === `error`) return `Error!`; |
|
return `Do Stuff`; |
|
}} |
|
className="btn btn-primary" |
|
loadingClassName="btn btn-secondary" |
|
completeClassName="btn btn-success" |
|
errorClassName="btn btn-danger" |
|
onClick={async () => { |
|
// TODO: Perform some asynchronous work |
|
}} |
|
onError={(error) => console.log(error)} |
|
/> |
|
); |
|
} |
|
|