useDelete
Headless React hook for single, batch, and confirmation-driven object deletion.
useDelete coordinates object deletion with your server API, featuring built-in support for confirmation dialog flows, single-step deletion, and batch operations.
"use client";
import { useDelete } from "@dimah-s3/react";
export function DeleteAvatar({ avatarKey }: { avatarKey: string }) {
const {
isConfirming,
requestDelete,
confirmDelete,
cancelDelete,
isDeleting,
} = useDelete({
route: "avatar",
onSuccess: (key) => {
console.log("Deleted avatar key:", key);
},
});
if (isConfirming) {
return (
<div className="flex items-center gap-2">
<span className="text-sm">Delete avatar?</span>
<button
type="button"
onClick={() => void confirmDelete()}
disabled={isDeleting}
className="text-destructive font-medium"
>
{isDeleting ? "Deleting…" : "Confirm"}
</button>
<button type="button" onClick={cancelDelete}>
Cancel
</button>
</div>
);
}
return (
<button type="button" onClick={() => requestDelete(avatarKey)}>
Delete Avatar
</button>
);
}Deletion workflows
Two-step confirmation flow
Use requestDelete(key) to enter the confirming state for that specific key. This keeps the pending key in state until the user approves (confirmDelete()) or cancels (cancelDelete()).
const { isConfirming, requestDelete, confirmDelete, cancelDelete } = useDelete({
route: "avatar",
});Immediate deletion (remove)
If your UI manages its own modal dialog or requires direct execution without internal confirmation state, call remove(key):
const { remove, isDeleting } = useDelete({ route: "avatar" });
await remove(avatarKey);Batch deletion (removeMany)
Delete multiple keys in a single request via S3 DeleteObjects:
const { removeMany } = useDelete({ route: "avatar" });
await removeMany([key1, key2, key3]);Type reference
import type {
UseDeleteOptions,
UseDeleteReturn,
UseDeleteState,
DeletePhase,
DeleteHooks,
} from "@dimah-s3/react";UseDeleteOptions
Prop
Type
UseDeleteReturn
Prop
Type
Frequently asked questions
Authorization runs securely on your server in delete.guard:
avatar: route({
delete: {
guard: async ({ key, request }) => {
const session = await getSession(request);
if (!session) throw errors.unauthorized();
const isOwner = await checkOwnership(session.userId, key);
if (!isOwner) throw errors.forbidden();
},
onDeleted: async ({ key, request }) => {
// Database record cleanup
},
},
});Call reset() to return the hook back to the idle phase:
const { reset, error } = useDelete({ route: "avatar" });
if (error) {
return (
<div>
<p className="text-destructive">{error.message}</p>
<button type="button" onClick={reset}>
Try Again
</button>
</div>
);
}