← Back to blog

Reusable alert dialog in Vue and React

6m read
  • Vue
  • React
  • Patterns

Every app eventually needs one of those "Are you sure?" dialogs. You click delete, and a little box pops up asking you to confirm. Simple enough, but if you've ever wired up that modal state by hand more than twice, you know how tedious it gets. A boolean to track visibility, a callback for confirm, another for cancel, and suddenly your component is drowning in dialog plumbing.

A better approach would be a Promise-based useAlert composable (Vue) or hook (React) that you can call like a regular async function. Call show(...), await the result, and get back true or false. No state management, no prop drilling, just a clean one-liner wherever you need confirmation.

The idea is simple. We create a single alert dialog component that lives at the root of our app. A shared piece of state holds the current dialog data and a resolve function. When you call useAlert().show(...), it sets that state and returns a Promise. The dialog appears, the user clicks OK or Cancel, and the Promise resolves. That's it.

The focus of this post is entirely on the logic, not the styling. We're using plain HTML and basic CSS here for clarity, but you could easily swap in any UI library (like Radix, Headless UI, shadcn/ui, etc.) for the modal itself and the underlying logic would stay exactly the same.

Below is the full implementation in both Vue and React.

composables/useAlert.ts
import { ref } from "vue";

export type AlertDialogProps = {
  data: {
    title?: string;
    message: string;
    confirmText?: string;
    cancelText?: string;
  };
  resolve: (value: boolean) => void;
};

const current = ref<AlertDialogProps>();

const show = (data: AlertDialogProps["data"]): Promise<boolean> => {
  return new Promise((resolve) => {
    current.value = { data, resolve };
  });
};

export const useAlert = () => {
  return { current, show };
};

And that's it. One shared piece of state, one Promise, and you've got a reusable confirmation dialog you can call from anywhere in your app with a single await. The pattern scales well too, you can extend AlertDialogProps to support different dialog types (info, warning, destructive) or custom actions, without changing how you call it. The API stays the same: await show(...)