4.4 낙관적 업데이트 패턴 적용(작성/수정) o

This commit is contained in:
koreacomp5
2025-10-09 15:18:50 +09:00
parent 1cc040a5d4
commit af206652cb
2 changed files with 82 additions and 1 deletions

View File

@@ -0,0 +1,81 @@
"use client";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { fetchJson } from "@/lib/api";
import { queryKeys } from "@/lib/queryKeys";
type CreateCommentInput = {
postId: string;
authorId?: string;
content: string;
isAnonymous?: boolean;
isSecret?: boolean;
};
export function useCreateComment(postId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (input: CreateCommentInput) =>
fetchJson<{ comment: any }>("/api/comments", {
method: "POST",
body: JSON.stringify(input),
}),
onMutate: async (input) => {
await qc.cancelQueries({ queryKey: queryKeys.comments.list(postId) });
const previous = qc.getQueryData<any>(queryKeys.comments.list(postId));
const optimistic = {
id: `temp-${Date.now()}`,
content: input.content,
isAnonymous: !!input.isAnonymous,
isSecret: !!input.isSecret,
createdAt: new Date().toISOString(),
};
qc.setQueryData(queryKeys.comments.list(postId), (old: any) => ({
comments: [optimistic, ...(old?.comments ?? [])],
}));
return { previous };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previous) qc.setQueryData(queryKeys.comments.list(postId), ctx.previous);
},
onSettled: () => {
qc.invalidateQueries({ queryKey: queryKeys.comments.list(postId) });
},
});
}
export function useTogglePin(postId: string) {
const qc = useQueryClient();
return useMutation({
mutationFn: async (vars: { pinned: boolean; order?: number | null; userIdHeader?: string }) =>
fetchJson<{ post: { id: string; isPinned: boolean; pinnedOrder: number | null } }>(
`/api/posts/${postId}/pin`,
{
method: "POST",
headers: vars.userIdHeader ? { "x-user-id": vars.userIdHeader } : undefined,
body: JSON.stringify({ pinned: vars.pinned, order: vars.order ?? null }),
}
),
onMutate: async (vars) => {
// Optimistic update detail cache
await qc.cancelQueries({ queryKey: queryKeys.posts.detail(postId) });
const previousDetail = qc.getQueryData<any>(queryKeys.posts.detail(postId));
qc.setQueryData(queryKeys.posts.detail(postId), (old: any) => ({
...(old ?? {}),
post: {
...(old?.post ?? { id: postId }),
isPinned: vars.pinned,
pinnedOrder: vars.pinned ? vars.order ?? 0 : null,
},
}));
return { previousDetail };
},
onError: (_err, _vars, ctx) => {
if (ctx?.previousDetail) qc.setQueryData(queryKeys.posts.detail(postId), ctx.previousDetail);
},
onSettled: () => {
qc.invalidateQueries({ queryKey: queryKeys.posts.detail(postId) });
},
});
}