Skip to content

Instantly share code, notes, and snippets.

@darylwright
Last active March 3, 2023 13:47
Show Gist options
  • Save darylwright/2354b24bd4fea58e1f05ba16790884ad to your computer and use it in GitHub Desktop.
Save darylwright/2354b24bd4fea58e1f05ba16790884ad to your computer and use it in GitHub Desktop.
A simple React HTML button for asynchronous onClick event handlers. Can change appearance and render button content differently depending on its loading state.
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)}
/>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment