4.4 낙관적 업데이트 패턴 적용(작성/수정) o
This commit is contained in:
81
src/lib/mutations/posts.ts
Normal file
81
src/lib/mutations/posts.ts
Normal 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) });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
4.1 React Query 설치 및 Provider 구성 o
|
4.1 React Query 설치 및 Provider 구성 o
|
||||||
4.2 공통 fetcher/에러 형식/재시도·백오프 설정 o
|
4.2 공통 fetcher/에러 형식/재시도·백오프 설정 o
|
||||||
4.3 캐시 키/무효화 전략 수립 및 적용 o
|
4.3 캐시 키/무효화 전략 수립 및 적용 o
|
||||||
4.4 낙관적 업데이트 패턴 적용(작성/수정)
|
4.4 낙관적 업데이트 패턴 적용(작성/수정) o
|
||||||
|
|
||||||
[공통/레이아웃]
|
[공통/레이아웃]
|
||||||
5.1 앱 레이아웃/헤더/푸터/사이드바 구성
|
5.1 앱 레이아웃/헤더/푸터/사이드바 구성
|
||||||
|
|||||||
Reference in New Issue
Block a user