ConfirmDialog
An imperative confirmation dialog with promise-based API for simple user confirmation flows. Provides a quick alternative to MUI Dialog for standard confirmation patterns.
Imperative Pattern
Quick Start
Two-step setup: wrap your app with the provider and call the hook
Quick Start
2 Steps
Step 1: Wrap your app with the provider
import { ConfirmDialogProvider } from '@dashforge/ui';
<ConfirmDialogProvider>
<App />
</ConfirmDialogProvider>Step 2: Use the hook in your components
import { useConfirm } from '@dashforge/ui';
const confirm = useConfirm();
const handleDelete = async () => {
const result = await confirm({
title: 'Delete User',
description: 'This action cannot be undone.',
});
if (result.status === 'confirmed') {
await deleteUser();
}
};Examples
Common ConfirmDialog patterns and configurations
Basic Confirmation
Simple confirm with title and description
const confirm = useConfirm();
const handleDelete = async () => {
const result = await confirm({
title: 'Delete User',
description: 'This action cannot be undone.',
});
if (result.status === 'confirmed') {
await deleteUser();
}
};Custom Buttons
Customize button labels and colors
const result = await confirm({
title: 'Proceed with Payment?',
description: 'You will be charged $99.99 immediately.',
confirmText: 'Pay Now',
cancelText: 'Go Back',
confirmButtonProps: {
color: 'success',
},
});
if (result.status === 'confirmed') {
await processPayment();
}Async Action Flow
Handle async operations after confirmation
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
const result = await confirm({
title: 'Submit Report?',
description: 'This will notify all stakeholders.',
});
if (result.status === 'confirmed') {
setLoading(true);
await api.submitReport(data);
setLoading(false);
}
};Form Dirty Check
Guard navigation with unsaved changes check
Form status: Has unsaved changes
const handleNavigate = async () => {
if (!isDirty) {
navigate('/next');
return;
}
const result = await confirm({
title: 'Discard Changes?',
description: 'You have unsaved changes.',
confirmText: 'Discard',
cancelText: 'Stay',
});
if (result.status === 'confirmed') {
navigate('/next');
}
};Understanding the Result
How to handle the discriminated union result from confirm()
The confirm function returns a semantic result that explicitly distinguishes between user actions and system behavior. Most of the time, you only need to check for confirmed status.
Recommended Pattern (90% of cases)
Check for confirmed, abort everything else
const result = await confirm({ title: 'Delete?' });
if (result.status === 'confirmed') {
// User confirmed - proceed
await performAction();
}
// Everything else = abort (simple!)Result Types
confirmed
User clicked confirm button
cancelled
User cancelled via button, backdrop, or ESC key
blocked
Another dialog is already open (re-entrant call)
Advanced: Track cancellation reasons
if (result.status === 'cancelled') {
analytics.track('dialog_cancelled', {
reason: result.reason // 'cancel-button', 'backdrop', 'escape-key'
});
}Integration Scenarios
Real-world integration patterns with navigation guards and async actions
Navigation Guard
Prevent navigation away from forms with unsaved changes
Form status: Has unsaved changes
const handleNavigate = async () => {
if (!isDirty) {
navigate('/next');
return;
}
const result = await confirm({
title: 'Discard Changes?',
description: 'You have unsaved changes.',
confirmText: 'Discard',
cancelText: 'Stay',
});
if (result.status === 'confirmed') {
navigate('/next');
}
};Form Submission
Confirm before submitting critical forms
Example pattern below
const onSubmit = async (data: FormData) => {
const result = await confirm({
title: 'Submit Report?',
description: 'This will send notifications to all stakeholders.',
});
if (result.status === 'confirmed') {
setSubmitting(true);
await api.submitReport(data);
toast.success('Report submitted');
navigate('/dashboard');
}
};Delete with Async Cleanup
Delete resource and clean up related data
Example pattern below
const handleDelete = async (projectId: string) => {
const result = await confirm({
title: 'Delete Project?',
description: 'This will permanently delete all associated data.',
confirmText: 'Delete',
confirmButtonProps: { color: 'error' },
});
if (result.status === 'confirmed') {
setDeleting(true);
await api.deleteProject(projectId);
await api.cleanupRelatedData(projectId);
toast.success('Project deleted');
queryClient.invalidateQueries(['projects']);
}
};API Reference
Complete API for ConfirmDialogProvider, useConfirm, and types
ConfirmDialogProvider
Context provider that enables useConfirm() hook. Wrap your app root or a subtree where you need confirmation dialogs.
| Prop | Type | Description |
|---|---|---|
| children | ReactNode | App content to wrap |
useConfirm()
Hook that returns the confirm function. Throws error if called outside ConfirmDialogProvider.
Returns
(options: ConfirmOptions) => Promise<ConfirmResult>
Imperative function that opens a confirmation dialog and returns a promise that resolves with the result.
ConfirmOptions
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| title | string | Yes | - | Dialog title |
| description | string | ReactNode | No | - | Dialog body content |
| confirmText | string | No | 'Confirm' | Confirm button label |
| cancelText | string | No | 'Cancel' | Cancel button label |
| confirmButtonProps | ButtonProps | No | - | Confirm button customization (color, variant, icon) |
| cancelButtonProps | ButtonProps | No | - | Cancel button customization |
| dialogProps | DialogProps | No | - | MUI Dialog props (limited subset: maxWidth, fullWidth, fullScreen) |
ConfirmResult
Discriminated union that distinguishes between user actions and system behavior.
type ConfirmResult =
| { status: 'confirmed' }
| {
status: 'cancelled';
reason: 'cancel-button' | 'backdrop' | 'escape-key' | 'provider-unmount';
}
| {
status: 'blocked';
reason: 'reentrant-call';
};Implementation Notes
Technical details, best practices, and usage guidelines
1
When NOT to Use ConfirmDialog
ConfirmDialog is designed for simple confirmation flows with standard layouts. For custom layouts, complex forms, or multi-step wizards, use MUI Dialog directly. ConfirmDialog provides a quick imperative API for common cases—it is NOT a replacement for all dialog use cases.
2
Re-entrancy Policy
Only one confirmation can be active at a time. Calling confirm() while another confirmation is pending will reject the previous promise with "cancelled" and immediately show the new confirmation. This prevents stacking multiple dialogs.
3
Promise Behavior
The confirm() hook returns a discriminated union result, not a boolean. Always check result.confirmed before proceeding. Cancelled confirmations return { confirmed: false, reason: "cancelled" } or { confirmed: false, reason: "closed" }.
4
Provider Scope
Wrap ConfirmDialogProvider at the highest level where confirmations are needed (typically at the app root or route layout). The useConfirm() hook must be called within a component tree wrapped by the provider.
5
Provider Unmount Behavior
If the provider unmounts while a confirmation is pending, the promise resolves with { confirmed: false, reason: "cancelled" }. This ensures cleanup and prevents memory leaks during navigation or conditional rendering.
6
Async Action Handling
For async actions (like API calls), handle loading states explicitly in your calling code. ConfirmDialog confirms user intent—it does not manage async execution. Show loading indicators or disable UI after confirmation as needed.
7
Form Integration Pattern
When using with forms, combine with form dirty state checks to guard navigation or unsaved changes. The confirmation should be triggered by your logic (e.g., beforeunload, navigation guard) rather than by the form itself.