HextaUI

Alert dialog: Async action

Return a promise from the onClick of <AlertDialogAction />. The button shows a spinner and the dialog can’t be closed until the promise settles. It closes when it resolves and stays open when it rejects, so people can try again.

"use client"

import * as React from "react"
import { IconTrash } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogMedia,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"

function wait(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms))
}

export function AlertDialogAsyncAction() {
  const [shouldFail, setShouldFail] = React.useState(false)
  const [attempts, setAttempts] = React.useState(0)

  return (
    <div className="flex flex-wrap items-center gap-2">
      <AlertDialog onOpenChange={(open) => open && setAttempts(0)}>
        <AlertDialogTrigger render={<Button variant="destructive" />}>
          Delete project
        </AlertDialogTrigger>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogMedia variant="destructive">
              <IconTrash />
            </AlertDialogMedia>
            <AlertDialogTitle>Delete “Acme website”?</AlertDialogTitle>
            <AlertDialogDescription>
              {attempts > 0
                ? `Deleting failed ${attempts} time${attempts > 1 ? "s" : ""}. Try again.`
                : "All deployments, domains and environment variables will be removed. This can’t be undone."}
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction
              variant="destructive"
              onClick={async () => {
                await wait(1500)
                if (shouldFail) {
                  setAttempts((count) => count + 1)
                  throw new Error("Request failed")
                }
              }}
            >
              Delete project
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
      <Button
        variant="ghost"
        size="sm"
        aria-pressed={shouldFail}
        onClick={() => setShouldFail(!shouldFail)}
      >
        Request will {shouldFail ? "fail" : "succeed"}
      </Button>
    </div>
  )
}

This adds the example as components/examples/alert-dialog/async-action.tsx, together with the Alert dialog component and the HextaUI theme tokens it needs.

pnpm dlx shadcn@latest add https://hextaui.com/r/alert-dialog-async-action.json

To add only the component, follow the Alert dialog installation.

Props in this example

The Alert dialog props this example sets, from the API reference.

PropTypeDefault
onOpenChangeNot called for closes blocked by a pending action.
(open: boolean, details) => void–
PropTypeDefault
render
ReactElement | (props, state) => ReactElement<button>
PropTypeDefault
variant
"default" | "destructive""default"
PropTypeDefault
onClickReturn a promise to show a spinner and keep the dialog open until it settles. Call event.preventDefault() to keep it open.
(event) => void | PromiseLike<unknown>–
variant
ButtonVariant"default"

Every prop is in the Alert dialog API reference.

See them all, with keyboard support and accessibility notes, on the Alert dialog page.