원클릭으로
frontend-mutation-hooks
Use when creating or using TanStack Query mutations for data modifications
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when creating or using TanStack Query mutations for data modifications
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Use when deploying Cloudflare Workers, managing R2 storage, or working with Cloudflare infrastructure
Use when working with ANTD components, theme tokens, icons, forms, or feedback components (message/notification/modal)
Use when adding, referencing, or serving static assets (images, fonts, videos, 3D models) through the R2 CDN pipeline with type-safe imports
Use when writing or reviewing JavaScript/TypeScript code for style patterns like concise arrows, inline handlers, expression formatting, or when tempted to use eslint-disable
Use when working with environment variables in frontend code
Use when creating or modifying keyboard shortcuts/hotkeys in frontend code
| name | frontend-mutation-hooks |
| description | Use when creating or using TanStack Query mutations for data modifications |
Clean, minimal mutation hooks for data modifications. NO comments - self-documenting code through naming and types.
Hook: useM_[Scope]_[EntityAction] | Variable: m[EntityAction] (drop scope)
const mProjectCreate = useM_PageOrganization_ProjectCreate();
mProjectCreate.mutation.mutate({ name: "New Project" });
Actions: Explicit suffix (no underscore): Create, Update, Delete, Upload
Folder: hooks/Page[Name]/useM_Page[Name]_[EntityAction].ts
// ✅ CORRECT - Never destructure
const sb_FromProjects_Insert = await supabase.from("projects").insert();
if (sb_FromProjects_Insert.error) throw sb_FromProjects_Insert.error;
return sb_FromProjects_Insert.data;
// ❌ WRONG
const { data, error } = await supabase.from("projects").insert();
import { useMutation } from "@tanstack/react-query";
import { supabase, type Supabase_InsertDto } from "@/configs/supabase/config";
import useApp from "antd/es/app/useApp";
export type UseM_PageOrganization_ProjectCreate_Params = Pick<
Supabase_InsertDto<"projects">,
"organization_id" | "name"
> &
Partial<Pick<Supabase_InsertDto<"projects">, "description">>;
export const useM_PageOrganization_ProjectCreate = () => {
const { message } = useApp();
const mutation = useMutation({
mutationFn: async (body: UseM_PageOrganization_ProjectCreate_Params) => {
const sb_FromProjects_Insert = await supabase
.from("projects")
.insert(body)
.select()
.single();
if (sb_FromProjects_Insert.error) throw sb_FromProjects_Insert.error;
return sb_FromProjects_Insert.data;
},
onSuccess: () => message.success("Project created successfully!"),
onError: (error) => {
console.error("Error creating project:", error);
message.error("Failed to create project!");
},
});
return { mutation };
};
Separates record ID (hook params) from mutable data (mutationFn params):
import type { AtLeastOne } from "@/types/utility.types";
export type UseM_PageOrganization_ProjectUpdate_Params = { projectId: string };
export type UseM_PageOrganization_ProjectUpdate_Body = AtLeastOne<
Pick<Supabase_UpdateDto<"projects">, "name" | "description">
>;
export const useM_PageOrganization_ProjectUpdate = ({
projectId,
}: UseM_PageOrganization_ProjectUpdate_Params) => {
const { message } = useApp();
const mutation = useMutation({
mutationKey: ["projects", "update", projectId],
mutationFn: async (body: UseM_PageOrganization_ProjectUpdate_Body) => {
const sb_FromProjects_Update = await supabase
.from("projects")
.update(body)
.eq("id", projectId)
.select()
.single();
if (sb_FromProjects_Update.error) throw sb_FromProjects_Update.error;
return sb_FromProjects_Update.data;
},
onSuccess: () => message.success("Project updated!"),
onError: (error) => {
console.error("Error updating project:", error);
message.error("Failed to update project!");
},
});
return { mutation };
};
| Aspect | Create | Update/Delete |
|---|---|---|
| Hook params | None () | Record ID ({ id }) |
| mutationFn params | Full body | Only mutable fields |
| Type exports | _Params only | _Params + _Body |
| mutationKey | Generic | Includes record ID |
const { modal } = App.useApp();
modal.confirm({
title: "Delete File",
content: `Delete "${fileName}"?`,
okType: "danger",
onOk: async () => await mFileDelete.mutation.mutateAsync({ fileId }),
});
mutateAsync: Use in modal.confirm() onOk, sequential operations
mutate: Normal button clicks, fire-and-forget
organizations, profiles): MUST manually invalidateonSuccess: () => {
queryClient.invalidateQueries({ queryKey: QueryKeys.organizations.all() });
},
// ❌ WRONG - logging twice
if (error) {
console.error(error);
throw error;
} // Logged here
onError: (error) => console.error(error); // And here!
// ✅ CORRECT - throw directly, let onError handle logging
if (error) throw error;
onError: (error) => {
console.error(error);
message.error("Failed!");
};
useM_[Scope]_[EntityAction]m[EntityAction]{ mutation } onlyonError onlyApp.useApp() for message({ entityId }), exports _Params + _Body, body uses AtLeastOnemPageOrganization_ProjectCreate → ✅ mProjectCreate