Modifying Existing Items
To update existing resources in the application, use the EditItem component. This component provides a dialog-based form that integrates with the backend API to persist changes and automatically refreshes the local data cache.
Updating an Item
The EditItem component is typically triggered from an actions menu. It requires the current item data and a callback to execute upon a successful update.
import { EditItem } from "@/components/Items/EditItem"
import { type ItemPublic } from "@/client"
// Example usage within a menu
export const ItemActionsMenu = ({ item }: { item: ItemPublic }) => {
const [open, setOpen] = useState(false)
return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button variant="ghost">Actions</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<EditItem
item={item}
onSuccess={() => setOpen(false)}
/>
</DropdownMenuContent>
</DropdownMenu>
)
}
Component Properties
The EditItem component accepts props defined by the EditItemProps interface in frontend/src/components/Items/EditItem.tsx:
item: AnItemPublicobject containing the currentid,title, anddescription. This is used to initialize the form fields.onSuccess: A function called after the update mutation completes successfully. In the standard implementation, this is used to close the parent dropdown menu.
Form Management and Validation
The component uses react-hook-form with a zod schema to manage state and validation.
Validation Schema
The schema ensures the title is present while the description remains optional:
const formSchema = z.object({
title: z.string().min(1, { message: "Title is required" }),
description: z.string().optional(),
})
Form Initialization
The form is initialized with the item's existing values. Note the handling of the description to ensure it is compatible with controlled inputs:
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
mode: "onBlur",
defaultValues: {
title: item.title,
description: item.description ?? undefined, // Converts null to undefined for the input
},
})
Data Persistence
Persistence is handled by a TanStack Query mutation that calls ItemsService.updateItem.
The Mutation Hook
The mutation sends a PUT request to the /api/v1/items/{id} endpoint and manages the UI state (toasts and loading indicators).
const mutation = useMutation({
mutationFn: (data: FormData) =>
ItemsService.updateItem({ id: item.id, requestBody: data }),
onSuccess: () => {
showSuccessToast("Item updated successfully")
setIsOpen(false)
onSuccess()
},
onError: handleError.bind(showErrorToast),
onSettled: () => {
// Refreshes the items list after the request finishes
queryClient.invalidateQueries({ queryKey: ["items"] })
},
})
API Service
The ItemsService.updateItem method (found in frontend/src/client/sdk.gen.ts) abstracts the HTTP request:
public static updateItem(data: ItemsUpdateItemData): CancelablePromise<ItemsUpdateItemResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/api/v1/items/{id}',
path: { id: data.id },
body: data.requestBody,
mediaType: 'application/json',
});
}
Troubleshooting and Gotchas
Validation Timing
The form is configured with mode: "onBlur". This means validation messages for the "Title" field will only appear once the user clicks out of the input field, rather than immediately as they type.
Dialog within Dropdowns
The EditItem component renders a DropdownMenuItem as its trigger. To prevent the dropdown menu from closing prematurely when the dialog is clicked, the component calls e.preventDefault() on the selection event:
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
onClick={() => setIsOpen(true)}
>
<Pencil />
Edit Item
</DropdownMenuItem>
Cache Invalidation
The onSettled callback ensures that queryClient.invalidateQueries({ queryKey: ["items"] }) is called regardless of whether the update succeeded or failed. This guarantees that the UI stays in sync with the server state, but it may cause a brief loading flicker in the parent list component as data is refetched.