Skip to content

Commit eca44df

Browse files
committed
Feat: 팔로우 등록/취소 낙관적업데이트 적용
1 parent 8ecf2f8 commit eca44df

File tree

4 files changed

+97
-42
lines changed

4 files changed

+97
-42
lines changed

src/components/UserProfile/UserProfileContent/index.tsx

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,33 @@ interface UserProfileContent {
1111
export const UserProfileContent = ({ userId }: UserProfileContent) => {
1212
const { completeResponses, isLoading } = useUserProfileQuery(Number(userId));
1313

14+
if (isLoading) {
15+
return (
16+
<div className="flex min-h-screen items-center justify-center">
17+
<Spinner className="size-28" />
18+
</div>
19+
);
20+
}
21+
1422
return (
1523
<div className="mt-8 grid grid-cols-3 gap-8">
16-
{isLoading ? (
17-
<div className="col-span-3 flex items-center justify-center">
18-
<Spinner />
19-
</div>
20-
) : (
21-
completeResponses.map((complete) =>
22-
complete.completePic ? (
23-
<Image
24-
key={complete.completeId}
25-
width={48}
26-
height={48}
27-
className="flex h-109 w-full rounded-4"
28-
priority
29-
src={complete.completePic}
30-
alt="인증한 이미지"
31-
/>
32-
) : (
33-
<div
34-
key={complete.completeId}
35-
className="flex h-109 w-full rounded-4 bg-gray-200"
36-
/>
37-
),
38-
)
24+
{completeResponses.map((complete) =>
25+
complete.completePic ? (
26+
<Image
27+
key={complete.completeId}
28+
width={48}
29+
height={48}
30+
className="flex h-109 w-full rounded-4"
31+
priority
32+
src={complete.completePic}
33+
alt="인증한 이미지"
34+
/>
35+
) : (
36+
<div
37+
key={complete.completeId}
38+
className="flex h-109 w-full rounded-4 bg-gray-200"
39+
/>
40+
),
3941
)}
4042
</div>
4143
);

src/components/UserProfile/UserProfileHeader/index.tsx

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { useRouter } from 'next/navigation';
55
import Image from 'next/image';
66
import { Button } from '@/components/common/Button/Button';
77
import { useUserProfileQuery } from '@/hooks/apis/Auth/useUserProfileQuery';
8-
// import { useAssignFollowMutation } from '@/hooks/apis/Follow/useAssignFollowMutation';
9-
// import { useDeleteFollowMutation } from '@/hooks/apis/Follow/useDeleteFollowMutation';
8+
import { useAssignFollowMutation } from '@/hooks/apis/Follow/useAssignFollowMutation';
9+
import { useDeleteFollowMutation } from '@/hooks/apis/Follow/useDeleteFollowMutation';
1010

1111
interface UserProfileHeader {
1212
userId: string;
@@ -16,19 +16,18 @@ export const UserProfileHeader = ({ userId }: UserProfileHeader) => {
1616
const router = useRouter();
1717

1818
const { name, profilePic, isFollow } = useUserProfileQuery(Number(userId));
19-
// const { mutate: assignFollow, isPending } = useAssignFollowMutation();
20-
// const { mutate: deleteFollow } = useDeleteFollowMutation();
19+
const { mutate: assignFollow } = useAssignFollowMutation();
20+
const { mutate: deleteFollow } = useDeleteFollowMutation();
2121

2222
const handleBack = () => {
2323
router.back();
2424
};
2525

2626
const handleClickFollow = () => {
2727
if (isFollow) {
28-
console.log('dddd');
28+
deleteFollow(Number(userId));
2929
} else {
30-
//assignFollow(Number(userId));
31-
//deleteFollow(Number(userId));
30+
assignFollow(Number(userId));
3231
}
3332
};
3433

@@ -53,9 +52,8 @@ export const UserProfileHeader = ({ userId }: UserProfileHeader) => {
5352
size="small"
5453
primary={isFollow ? false : true}
5554
onClick={handleClickFollow}
56-
// pending={isPending}
5755
>
58-
{isFollow ? '팔로우' : '팔로잉'}
56+
{isFollow === true ? '팔로잉' : '팔로우'}
5957
</Button>
6058
</div>
6159
</>

src/hooks/apis/Follow/useAssignFollowMutation.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useMutation, useQueryClient } from '@tanstack/react-query';
22
import { POST } from '@/apis/services/httpMethod';
33
import { API_ENDPOINTS } from '@/constants/ApiEndpoints';
4-
import { AssignFollowResponse } from '@/types/response';
4+
import { AssignFollowResponse, UserProfileResponse } from '@/types/response';
55
import { notify } from '@/store/useToastStore';
66
import { QUERY_KEYS } from '@/constants/QueryKeys';
77

@@ -17,13 +17,40 @@ export const useAssignFollowMutation = () => {
1717
POST<AssignFollowResponse, FollowId>(
1818
API_ENDPOINTS.FOLLOW.ASSIGN_FOLLOW(userId),
1919
),
20-
onSuccess: (data) => {
21-
notify('success', '팔로우 등록', 3000);
20+
onMutate: async (userId) => {
21+
const previousData = queryClient.getQueriesData<UserProfileResponse>({
22+
queryKey: [QUERY_KEYS.USER_PROFILE, userId],
23+
});
24+
25+
await queryClient.cancelQueries({
26+
queryKey: [QUERY_KEYS.USER_PROFILE, userId],
27+
});
28+
29+
queryClient.setQueryData(
30+
[QUERY_KEYS.USER_PROFILE, userId],
31+
(oldData: UserProfileResponse) => ({
32+
...oldData,
33+
data: {
34+
...oldData.data,
35+
isFollow: true,
36+
},
37+
}),
38+
);
39+
return { previousData };
40+
},
41+
onSettled: (userId) => {
2242
queryClient.invalidateQueries({
23-
queryKey: [QUERY_KEYS.USER_PROFILE, data.data.followId],
43+
queryKey: [QUERY_KEYS.USER_PROFILE, userId],
2444
});
2545
},
26-
onError: (error) => {
46+
onSuccess: () => {
47+
notify('success', '팔로우 등록', 3000);
48+
},
49+
onError: (error, userId, context) => {
50+
queryClient.setQueryData(
51+
[QUERY_KEYS.USER_PROFILE, userId],
52+
context?.previousData,
53+
);
2754
console.error(error.message);
2855
notify('error', '팔로우 등록 실패', 3000);
2956
},

src/hooks/apis/Follow/useDeleteFollowMutation.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useMutation, useQueryClient } from '@tanstack/react-query';
22
import { POST } from '@/apis/services/httpMethod';
33
import { API_ENDPOINTS } from '@/constants/ApiEndpoints';
4-
import { DeleteFollowResponse } from '@/types/response';
4+
import { DeleteFollowResponse, UserProfileResponse } from '@/types/response';
55
import { notify } from '@/store/useToastStore';
66
import { QUERY_KEYS } from '@/constants/QueryKeys';
77

@@ -17,13 +17,41 @@ export const useDeleteFollowMutation = () => {
1717
POST<DeleteFollowResponse, FollowId>(
1818
API_ENDPOINTS.FOLLOW.DELETE_FOLLOW(userId),
1919
),
20-
onSuccess: (data) => {
21-
notify('success', '팔로우 취소', 3000);
20+
onMutate: async (userId) => {
21+
const previousData = queryClient.getQueriesData<UserProfileResponse>({
22+
queryKey: [QUERY_KEYS.USER_PROFILE, userId],
23+
});
24+
25+
await queryClient.cancelQueries({
26+
queryKey: [QUERY_KEYS.USER_PROFILE, userId],
27+
});
28+
29+
queryClient.setQueryData(
30+
[QUERY_KEYS.USER_PROFILE, userId],
31+
(oldData: UserProfileResponse) => ({
32+
...oldData,
33+
data: {
34+
...oldData.data,
35+
isFollow: false,
36+
},
37+
}),
38+
);
39+
return { previousData };
40+
},
41+
42+
onSettled: (userId) => {
2243
queryClient.invalidateQueries({
23-
queryKey: [QUERY_KEYS.USER_PROFILE, data.data.followerId],
44+
queryKey: [QUERY_KEYS.USER_PROFILE, userId],
2445
});
2546
},
26-
onError: (error) => {
47+
onSuccess: () => {
48+
notify('success', '팔로우 취소', 3000);
49+
},
50+
onError: (error, userId, context) => {
51+
queryClient.setQueryData(
52+
[QUERY_KEYS.USER_PROFILE, userId],
53+
context?.previousData,
54+
);
2755
console.error(error.message);
2856
notify('error', '팔로우 취소 실패', 3000);
2957
},

0 commit comments

Comments
 (0)