Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 5 additions & 14 deletions src/pages/result/components/matching-receive-view.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { alarmMutations } from '@apis/alarm/alarm-mutations';
import { matchMutations } from '@apis/match/match-mutations';
import { matchQueries } from '@apis/match/match-queries';
import Button from '@components/button/button/button';
Expand Down Expand Up @@ -27,7 +26,6 @@ const MatchingReceiveView = () => {

const parsedId = Number(matchId);
const { mutate: acceptMatch } = useMutation(matchMutations.MATCH_ACCEPT());
const { mutate: readAlarm } = useMutation(alarmMutations.READ_ALARM());
const { mutate: rejectMatch } = useMutation(matchMutations.MATCH_REJECT());
const { data, isError } = useQuery(matchQueries.MATCH_DETAIL(parsedId, true));

Expand Down Expand Up @@ -62,18 +60,11 @@ const MatchingReceiveView = () => {
const handleAccept = () => {
acceptMatch(parsedId, {
onSuccess: () => {
readAlarm(parsedId, {
onSuccess: () => {
if (cardType === 'group') {
navigate(`${ROUTES.MATCH}?tab=그룹&filter=전체`);
} else {
navigate(`${ROUTES.RESULT(matchId)}?type=success`);
}
},
onError: () => {
navigate(ROUTES.ERROR);
},
});
if (isGroupMatching) {
navigate(`${ROUTES.MATCH}?tab=그룹&filter=전체`);
} else {
navigate(`${ROUTES.RESULT(matchId)}?type=success`);
}

fillTabItems.forEach((label) => {
queryClient.invalidateQueries({ queryKey: MATCH_KEY.STATUS.SINGLE(label) });
Expand Down
9 changes: 4 additions & 5 deletions src/shared/apis/alarm/alarm-mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { END_POINT } from '@constants/api';
import { ALARM_KEY } from '@constants/query-key';
import queryClient from '@libs/query-client';
import { mutationOptions } from '@tanstack/react-query';
import type { postReadAlarmResponse } from '@/shared/types/alarm-types';

export const alarmMutations = {
READ_ALARM: () =>
mutationOptions<postReadAlarmResponse, Error, number>({
mutationKey: ALARM_KEY.READ(),
mutationFn: (matchId) => post(END_POINT.POST_READ_ALARM(matchId)),
READ_ALL_ALARMS: () =>
mutationOptions<Error, void>({
mutationKey: ALARM_KEY.READ_ALL(),
mutationFn: () => post(END_POINT.POST_READ_ALL_ALARMS),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
},
Comment on lines +12 to 17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

낙관적 업데이트 롤백 누락 → 실패 시 배지가 계속 숨겨질 수 있음

API 실패해도 HAS_UNREAD를 false로 유지합니다. 이전 값 스냅샷 저장 → onError 롤백, 무조건 onSettled에서 invalidate 하도록 바꿔주세요. 컨텍스트 타입 제네릭(4번째)도 명시 필요.

-  READ_ALL_ALARMS: () =>
-    mutationOptions<void, Error, void>({
+  READ_ALL_ALARMS: () =>
+    mutationOptions<void, Error, void, { previous?: boolean }>({
       mutationKey: ALARM_KEY.READ_ALL(),
       mutationFn: () => post<void>(END_POINT.POST_READ_ALL_ALARMS),
-      onMutate: async () => {
-        queryClient.setQueryData(ALARM_KEY.HAS_UNREAD, false);
-      },
-      onSuccess: () => {
-        queryClient.invalidateQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
-      },
+      onMutate: async () => {
+        await queryClient.cancelQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
+        const previous = queryClient.getQueryData<boolean>(ALARM_KEY.HAS_UNREAD);
+        queryClient.setQueryData<boolean>(ALARM_KEY.HAS_UNREAD, false);
+        return { previous };
+      },
+      onError: (_err, _vars, ctx) => {
+        if (ctx?.previous !== undefined) {
+          queryClient.setQueryData(ALARM_KEY.HAS_UNREAD, ctx.previous);
+        }
+      },
+      onSettled: () => {
+        queryClient.invalidateQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
+      },
     }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onMutate: async () => {
queryClient.setQueryData(ALARM_KEY.HAS_UNREAD, false);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
},
export const alarmMutations = {
READ_ALL_ALARMS: () =>
mutationOptions<void, Error, void, { previous?: boolean }>({
mutationKey: ALARM_KEY.READ_ALL(),
mutationFn: () => post<void>(END_POINT.POST_READ_ALL_ALARMS),
// Snapshot and clear unread flag optimistically
onMutate: async () => {
await queryClient.cancelQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
const previous = queryClient.getQueryData<boolean>(ALARM_KEY.HAS_UNREAD);
queryClient.setQueryData<boolean>(ALARM_KEY.HAS_UNREAD, false);
return { previous };
},
// Roll back if the mutation fails
onError: (_err, _vars, ctx) => {
if (ctx?.previous !== undefined) {
queryClient.setQueryData(ALARM_KEY.HAS_UNREAD, ctx.previous);
}
},
// Always re-fetch after either success or error
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ALARM_KEY.HAS_UNREAD });
},
}),
// …other mutations
};
🤖 Prompt for AI Agents
In src/shared/apis/alarm/alarm-mutations.ts around lines 12 to 17, the
optimistic update currently sets HAS_UNREAD to false without saving a snapshot
or rolling back on failure and only invalidates onSuccess; change onMutate to
capture and return the previous value snapshot (as the mutation context),
annotate the useMutation call with the four generics including the context type,
implement onError to restore the previous value from context (rollback), and
move the queryClient.invalidateQueries({ queryKey: ALARM_KEY.HAS_UNREAD }) into
onSettled so the cache is always refreshed after success or failure.

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { alarmMutations } from '@apis/alarm/alarm-mutations';
import { alarmQueries } from '@apis/alarm/alarm-queries';
import { NAV_ITEMS } from '@components/bottom-navigation/constants/bottom-navigation';
import Icon from '@components/icon/icon';
import useAuth from '@hooks/use-auth';
import { cn } from '@libs/cn';
import { ROUTES } from '@routes/routes-config';
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useLocation, useNavigate } from 'react-router-dom';

const BottomNavigation = () => {
Expand All @@ -14,6 +15,8 @@ const BottomNavigation = () => {

const { data: hasUnreadAlarms } = useQuery(alarmQueries.HAS_UNREAD());

const readAllAlarmsMutation = useMutation(alarmMutations.READ_ALL_ALARMS());

const isActive = (path: string) => pathname === path;

const isDisabled = (path: string) => {
Expand All @@ -22,6 +25,11 @@ const BottomNavigation = () => {

const handleTabClick = (path: string) => {
if (isDisabled(path)) return;

if (path === ROUTES.MATCH && hasUnreadAlarms) {
readAllAlarmsMutation.mutate();
}

navigate(path);
};

Expand Down
1 change: 1 addition & 0 deletions src/shared/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ export const END_POINT = {
// 알림
GET_UNREAD_ALARMS: '/v2/users/alarm',
POST_READ_ALARM: (matchId: number | string) => `/v2/users/alarm/${matchId}`,
POST_READ_ALL_ALARMS: '/v2/users/alarms',
};
2 changes: 1 addition & 1 deletion src/shared/constants/query-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,5 @@ export const MATCH_KEY = {

export const ALARM_KEY = {
HAS_UNREAD: ['alarms', 'hasUnread'] as const,
READ: () => ['alarms', 'read'] as const,
READ_ALL: () => ['alarms', 'read-all'] as const,
};