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
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
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.