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

Editing a Project

Editing a Project

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

EXACTLY the same pattern as useCreateProject (chapter 78), this time for PATCH – the REST semantics from chapter 10 carry over 1:1 to the hook structure.

Writing the update hook

src/hooks/useUpdateProject.ts
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../api/client';
import type { Project } from '../types/project';

interface UpdateProjectInput {
  id: number;
  name?: string;
  description?: string;
}

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

  return useMutation({
    mutationFn: async ({ id, ...input }: UpdateProjectInput) => {
      const response = await apiClient.patch<Project>(
        `/projects/${id}`,
        input,
        { headers: { 'Content-Type': 'application/merge-patch+json' } },
      );

      return response.data;
    },
    onSuccess: (updatedProject) => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
      queryClient.setQueryData(['project', updatedProject.id], updatedProject);
    },
  });
}

Achtung: Content-Type: application/merge-patch+json MUST be set EXPLICITLY – the default header from chapter 74 is application/ld+json, which does NOT give the semantics expected by chapter 10 for PATCH.

setQueryData IN ADDITION to invalidateQueries: THIS updates the cache IMMEDIATELY with the server response (NO extra request needed, since the CURRENT data is ALREADY available), WHILE invalidateQueries reloads the LIST in the background.

An editable form

src/components/EditProjectForm.tsx
import { useState, type FormEvent } from 'react';
import { useUpdateProject } from '../hooks/useUpdateProject';
import type { Project } from '../types/project';

function EditProjectForm({ project }: { project: Project }) {
  const [name, setName] = useState(project.name);
  const updateProject = useUpdateProject();

  function handleSubmit(event: FormEvent) {
    event.preventDefault();
    updateProject.mutate({ id: project.id, name });
  }

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button type="submit" disabled={updateProject.isPending}>
        Save
      </button>
      {updateProject.isSuccess && <p>Saved!</p>}
    </form>
  );
}

export default EditProjectForm;

useState(project.name) initializes the form WITH the CURRENT values – EXACTLY like a classic Symfony FormType (createForm(ProjectType::class, $project)), just MANUALLY replicated on the React side.

Tipp: isSuccess IN ADDITION to isPending/isError – TanStack Query MODELS the ENTIRE lifecycle of a mutation (idlependingsuccess/error), USEFUL for brief feedback like "Saved!".