Skip to main content

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 the deleteItem wrapper which invokes the SDK.
  • onSuccess: Triggers a success toast and executes the onSuccess callback passed via props (typically used to close parent menus).
  • onError: Uses the handleError utility bound to showErrorToast to extract and display API error messages.
  • onSettled: Calls queryClient.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, the DropdownMenuItem uses onSelect={(e) => e.preventDefault()}. This is critical because it prevents the dropdown menu from closing before the confirmation Dialog has a chance to open.
  • Global Cache Invalidation: The use of queryClient.invalidateQueries() in onSettled triggers 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 handleError utility in frontend/src/utils.ts expects to be bound to a function that accepts a string (like showErrorToast). Failure to use .bind(showErrorToast) will result in the error message not being displayed correctly.