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

Creating a New Project (Mutation)

Creating a New Project (Mutation)

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

Chapter 76 already showed useMutation FOR the login – this chapter shows the SECOND, equally IMPORTANT pattern: a mutation that UPDATES the CACHE of an EXISTING query.

Writing the create hook

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

interface CreateProjectInput {
  name: string;
  description?: string;
}

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

  return useMutation({
    mutationFn: async (input: CreateProjectInput) => {
      const response = await apiClient.post<Project>('/projects', input);

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

invalidateQueries({ queryKey: ['projects'] }) is the DECISIVE step: AFTER a successful creation, TanStack Query marks the cache entry from chapter 75 as STALE, which triggers an AUTOMATIC refetch – the list shows the NEW project, WITHOUT ProjectListPage having to know ANYTHING about it.

Building the form

src/components/CreateProjectForm.tsx
import { useState, type FormEvent } from 'react';
import { useCreateProject } from '../hooks/useCreateProject';

function CreateProjectForm() {
  const [name, setName] = useState('');
  const createProject = useCreateProject();

  function handleSubmit(event: FormEvent) {
    event.preventDefault();
    createProject.mutate(
      { name },
      { onSuccess: () => setName('') },
    );
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Project name"
      />
      <button type="submit" disabled={createProject.isPending}>
        {createProject.isPending ? 'Creating...' : 'Create project'}
      </button>
    </form>
  );
}

export default CreateProjectForm;

mutate(input, { onSuccess }) accepts a SECOND, CALL-SPECIFIC callback IN ADDITION to the onSuccess in the hook ITSELF (chapter-wide) – BOTH get EXECUTED, the hook's callback FIRST.

owner gets set automatically

NO owner field in the form – EXACTLY as planned in chapter 54, ProjectOwnerProcessor handles that AUTOMATICALLY server-side, the frontend does NOT need to worry ABOUT this logic.

Tipp: invalidateQueries is the SIMPLEST, but NOT always the MOST EFFICIENT strategy – TanStack Query ALSO offers "optimistic updates" (updating the cache IMMEDIATELY, BEFORE the server response), which is NOT needed FOR OUR project, but makes a noticeable performance difference in VERY interactive UIs.