Resource Deletion Workflow
To implement a resource deletion workflow that includes a confirmation dialog, API interaction, and automatic UI updates, use the DeleteItem component in conjunction with the ItemsService.
Implementation
The DeleteItem component manages the deletion lifecycle using TanStack Query's useMutation and provides a destructive confirmation dialog using Shadcn/UI components.
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { Trash2 } from "lucide-react"
import { ItemsService } from "@/client"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"
import { LoadingButton } from "@/components/ui/loading-button"
import useCustomToast from "@/hooks/useCustomToast"
import { handleError } from "@/utils"
interface DeleteItemProps {
id: string
onSuccess: () => void
}
const DeleteItem = ({ id, onSuccess }: DeleteItemProps) => {
const [isOpen, setIsOpen] = useState(false)
const queryClient = useQueryClient()
const { showSuccessToast, showErrorToast } = useCustomToast()
const { handleSubmit } = useForm()
const deleteItem = async (id: string) => {
await ItemsService.deleteItem({ id: id })
}
const mutation = useMutation({
mutationFn: deleteItem,
onSuccess: () => {
showSuccessToast("The item was deleted successfully")
setIsOpen(false)
onSuccess()
},
onError: handleError.bind(showErrorToast),
onSettled: () => {
queryClient.invalidateQueries()
},
})
const onSubmit = async () => {
mutation.mutate(id)
}
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DropdownMenuItem
variant="destructive"
onSelect={(e) => e.preventDefault()}
onClick={() => setIsOpen(true)}
>
<Trash2 />
Delete Item
</DropdownMenuItem>
<DialogContent className="sm:max-w-md">
<form onSubmit={handleSubmit(onSubmit)}>
<DialogHeader>
<DialogTitle>Delete Item</DialogTitle>
<DialogDescription>
This item will be permanently deleted. Are you sure? You will not
be able to undo this action.
</DialogDescription>
</DialogHeader>
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button variant="outline" disabled={mutation.isPending}>
Cancel
</Button>
</DialogClose>
<LoadingButton
variant="destructive"
type="submit"
loading={mutation.isPending}
>
Delete
</LoadingButton>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}
export default DeleteItem
Key Workflow Components
1. API Interaction via ItemsService
The component uses the auto-generated ItemsService.deleteItem method from frontend/src/client/sdk.gen.ts. This method performs a DELETE request to /api/v1/items/{id}.
2. Mutation Lifecycle Management
The useMutation hook handles the asynchronous state of the deletion:
mutationFn: Calls thedeleteItemwrapper which invokes the SDK.onSuccess: Triggers a success toast and executes theonSuccesscallback passed via props (typically used to close parent menus).onError: Uses thehandleErrorutility bound toshowErrorToastto extract and display API error messages.onSettled: CallsqueryClient.invalidateQueries(). This ensures that after a deletion, all active queries (like the item list) are marked as stale and refetched, keeping the UI in sync with the server.
3. Destructive Confirmation UI
The workflow uses a Dialog to prevent accidental deletions. The LoadingButton component is used for the "Delete" action to provide visual feedback while mutation.isPending is true.
Integration in Data Tables
The DeleteItem component is designed to be used within action menus, such as the ItemActionsMenu found in frontend/src/components/Items/ItemActionsMenu.tsx.
import { ItemPublic } from "@/client"
import DeleteItem from "../Items/DeleteItem"
export const ItemActionsMenu = ({ item }: { item: ItemPublic }) => {
const [open, setOpen] = useState(false)
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
{/* ... other menu items ... */}
<DropdownMenuContent align="end">
<DeleteItem id={item.id} onSuccess={() => setOpen(false)} />
</DropdownMenuContent>
</DropdownMenu>
)
}
Troubleshooting and Gotchas
- Dropdown Closing Prematurely: In
DeleteItem, theDropdownMenuItemusesonSelect={(e) => e.preventDefault()}. This is critical because it prevents the dropdown menu from closing before the confirmationDialoghas a chance to open. - Global Cache Invalidation: The use of
queryClient.invalidateQueries()inonSettledtriggers a refetch of all active queries. While this ensures data consistency across the application, it may cause multiple network requests if many different data sets are currently mounted. - Error Handling Context: The
handleErrorutility infrontend/src/utils.tsexpects to be bound to a function that accepts a string (likeshowErrorToast). Failure to use.bind(showErrorToast)will result in the error message not being displayed correctly.