Action Button
A button that performs an action when clicked and optionally asks for user confirmation.
import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: false }}
export function BasicActionButton() { return ( <ActionButton action={serverAction} requireAreYouSure> Do action </ActionButton> )}
Installation
Section titled “Installation”This component relies on other items which must be installed first.
Copy and paste the following code into your project.
components/ui/action-button.tsx
"use client"
import { type ComponentProps, type ReactNode, useTransition } from "react"import { Button } from "@/components/ui/button"import { toast } from "sonner"import { LoadingSwap } from "@/components/ui/loading-swap"import { AlertDialog, AlertDialogDescription, AlertDialogTitle, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogFooter, AlertDialogHeader, AlertDialogTrigger,} from "@/components/ui/alert-dialog"
export function ActionButton({ action, requireAreYouSure = false, areYouSureDescription = "This action cannot be undone.", ...props}: ComponentProps<typeof Button> & { action: () => Promise<{ error: boolean; message?: string }> requireAreYouSure?: boolean areYouSureDescription?: ReactNode}) { const [isLoading, startTransition] = useTransition()
function performAction() { startTransition(async () => { const data = await action() if (data.error) toast.error(data.message ?? "Error") }) }
if (requireAreYouSure) { return ( <AlertDialog open={isLoading ? true : undefined}> <AlertDialogTrigger asChild> <Button {...props} /> </AlertDialogTrigger> <AlertDialogContent> <AlertDialogHeader> <AlertDialogTitle>Are you sure?</AlertDialogTitle> <AlertDialogDescription> {areYouSureDescription} </AlertDialogDescription> </AlertDialogHeader> <AlertDialogFooter> <AlertDialogCancel>Cancel</AlertDialogCancel> <AlertDialogAction disabled={isLoading} onClick={performAction}> <LoadingSwap isLoading={isLoading}>Yes</LoadingSwap> </AlertDialogAction> </AlertDialogFooter> </AlertDialogContent> </AlertDialog> ) }
return ( <Button {...props} disabled={props.disabled ?? isLoading} onClick={e => { performAction() props.onClick?.(e) }} > <LoadingSwap isLoading={isLoading} className="inline-flex items-center gap-2" > {props.children} </LoadingSwap> </Button> )}
Update the import paths to match your project setup.
import { ActionButton } from "@/components/ui/action-button"
import { updateSettings } from "@/actions/settings"
const settings = { name: "Kyle" }
<ActionButton action={updateSettings.bind(null, settings)} requireAreYouSure> Save Settings</ActionButton>
Examples
Section titled “Examples”Default
Section titled “Default”import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: false }}
export function DefaultActionButton() { return <ActionButton action={serverAction}>Do default behavior</ActionButton>}
Require Are You Sure
Section titled “Require Are You Sure”import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: false }}
export function AreYouSureActionButton() { return ( <ActionButton action={serverAction} requireAreYouSure areYouSureDescription="I can put anything I want here." > Do extra secure action </ActionButton> )}
With Error
Section titled “With Error”import { ActionButton } from "@/components/ui/action-button"
const serverAction = async () => { // Simulate a server action await new Promise(resolve => setTimeout(resolve, 1000)) return { error: true, message: "Something went wrong. Please try again later.", }}
export function ErrorActionButton() { return ( <ActionButton variant="destructive" action={serverAction}> Do error action </ActionButton> )}