Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Deleting a Project

Deleting a Project

~12 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

Deleting is IRREVERSIBLE (chapter 44: cascade: ['remove'] ALSO deletes all tasks) – a confirmation dialog is ESPECIALLY important HERE.

Writing the delete hook

src/hooks/useDeleteProject.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../api/client';

export function useDeleteProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: async (id: number) => {
      await apiClient.delete(`/projects/${id}`);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
    },
  });
}

EXACTLY as chapter 10 showed: DELETE returns 204 No Content, NO response body – mutationFn correspondingly returns NOTHING (Promise<void>).

A confirmation dialog

function DeleteProjectButton({ projectId }: { projectId: number }) {
  const deleteProject = useDeleteProject();

  function handleClick() {
    const confirmed = window.confirm(
      'Really delete this project? All related tasks will also be deleted.',
    );

    if (confirmed) {
      deleteProject.mutate(projectId);
    }
  }

  return (
    <button onClick={handleClick} disabled={deleteProject.isPending}>
      Delete
    </button>
  );
}

Achtung: window.confirm is the SIMPLEST solution, but blocks the ENTIRE browser tab and can NOT be styled – for a PRODUCTION-READY UI, a CUSTOM modal component would be the BETTER choice. For OUR learning project, the native dialog is COMPLETELY sufficient.

Handling 403 correctly

The voter from chapters 52-53 can REJECT a deletion with 403 (e.g. if the deleter is NOT the owner) – the GLOBAL 401 interceptor from chapter 77 does NOT kick in HERE (403 is NOT an authentication error), but the hook's isError is ENOUGH to show a matching message.

Tipp: EXACTLY as chapter 53 introduced securityMessage for server-side error messages, the frontend can read error.response?.data."hydra:description" to show THIS message DIRECTLY in the UI, instead of inventing a CUSTOM, generic error message.