Button with Loading Spinner

Reusable React button component that shows a spinner when performing an async action.

When actions take time—like submitting a form or saving settings—users appreciate visual feedback. This button component accepts an onClick callback that returns a promise; while the promise is pending, it disables itself and displays a spinner. It’s simple, accessible, and easy to reuse across your app.

import { useState } from "react";
 
interface LoadingButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  onClick: () => Promise<unknown>;
  children: React.ReactNode;
}
 
export function LoadingButton({
  onClick,
  children,
  ...rest
}: LoadingButtonProps) {
  const [loading, setLoading] = useState(false);
  const handleClick = async () => {
    setLoading(true);
    try {
      await onClick();
    } finally {
      setLoading(false);
    }
  };
  return (
    <button onClick={handleClick} disabled={loading} {...rest}>
      {loading ? <span className="spinner" aria-label="Loading…" /> : children}
    </button>
  );
}

You can style the .spinner class however you like, perhaps with CSS animations. Use LoadingButton whenever you need to show progress without rewriting the same boilerplate each time.