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.