react-confirm-action
v0.1.0
Published
Headless inline confirmation for async React actions
Downloads
138
Maintainers
Readme
react-confirm-action
Inline confirmation for React actions.
Instead of opening a modal before a destructive action, the button asks the user to click again before the action runs.
<ConfirmButton action={deleteProject}>
Delete project
</ConfirmButton>Flow:
Delete project
→ Click again to confirm
→ Pending...
→ Delete projectIf the second click does not happen before the timeout, the button returns to its initial state.
Installation
npm install react-confirm-actionConfirmButton
The quickest way to use the package:
import { ConfirmButton } from "react-confirm-action";
function DeleteProjectButton() {
const deleteProject = async () => {
await fetch("/api/project", {
method: "DELETE",
});
};
return (
<ConfirmButton
action={deleteProject}
confirmText="Click again to delete"
pendingText="Deleting..."
errorText="Try again"
timeout={3000}
>
Delete project
</ConfirmButton>
);
}Props
action
Function that runs after the second click.
action: () => void | Promise<void>timeout
Time in milliseconds during which the second click is accepted.
Default:
3000confirmText
Content shown after the first click.
Default:
Click again to confirmpendingText
Content shown while the action is running.
Default:
Pending...errorText
Content shown when the action throws or returns a rejected Promise.
Default:
Try againConfirmButton also accepts standard HTML button props such as:
<ConfirmButton
action={deleteProject}
className="danger"
type="button"
aria-label="Delete project"
>
Delete
</ConfirmButton>The button is automatically disabled while the action is pending.
useConfirmAction
For full control over the UI, use the headless hook directly.
import { useConfirmAction } from "react-confirm-action";
function DeleteProjectButton() {
const { state, error, trigger, reset } = useConfirmAction({
action: async () => {
await fetch("/api/project", {
method: "DELETE",
});
},
timeout: 3000,
});
return (
<>
<button
onClick={() => void trigger()}
disabled={state === "pending"}
>
{state === "idle" && "Delete project"}
{state === "confirming" && "Click again to delete"}
{state === "pending" && "Deleting..."}
{state === "error" && "Try again"}
</button>
{error instanceof Error && (
<p>{error.message}</p>
)}
{state !== "idle" && (
<button onClick={reset}>
Reset
</button>
)}
</>
);
}States
useConfirmAction exposes four states:
type ConfirmActionState =
| "idle"
| "confirming"
| "pending"
| "error";Flow:
idle
↓ first trigger
confirming
↓ second trigger
pending
↓ success ↓ failure
idle errorIf the confirmation timeout expires:
confirming
→ idleCalling trigger() while the action is already pending does not run the action again.
Why inline confirmation?
Confirmation dialogs are useful when an action needs additional context or a strong interruption.
For smaller destructive actions, a modal can be unnecessarily disruptive.
Inline confirmation keeps the interaction in place:
Delete
→ Click again to deleteTypical use cases include:
- deleting an item;
- disconnecting an integration;
- revoking an API key;
- resetting settings;
- removing a member;
- cancelling an operation.
Headless by default
The core useConfirmAction hook does not provide styles or layout.
You control:
- button styles;
- icons;
- animations;
- labels;
- error rendering;
- surrounding UI.
The package manages:
- confirmation state;
- confirmation timeout;
- async action lifecycle;
- pending state;
- errors;
- protection from repeated execution.
Requirements
- React 18 or newer
- React DOM 18 or newer
License
MIT
