diff --git a/src/app/(routes)/user/[userId]/page.tsx b/src/app/(routes)/user/[userId]/page.tsx index e7c0c9f1..c5416c4e 100644 --- a/src/app/(routes)/user/[userId]/page.tsx +++ b/src/app/(routes)/user/[userId]/page.tsx @@ -1,20 +1,24 @@ +'use client' + import { Avatar, CategoryListItem, FollowListButton, ProfileEditButton, + Spinner, } from '@/components' import { CATEGORIES_RENDER, PROFILE_MSG } from '@/constants' -import { fetchGetUserProfile } from '@/services/users/useUsers' +import { useGetUserProfile } from '@/services/users/useUsers' import { UserLayoutProps } from './layout' -export default async function UserPage({ - params: { userId }, -}: UserLayoutProps) { - const user = await fetchGetUserProfile({ memberId: userId }) +export default function UserPage({ params: { userId } }: UserLayoutProps) { + const { data: user, isFetching: isUserLoading } = useGetUserProfile( + Number(userId), + ) return ( <> + {isUserLoading && }
{user?.profileImagePath && ( diff --git a/src/components/FollowListButton/FollowListButton.tsx b/src/components/FollowListButton/FollowListButton.tsx index 5bcf48ee..cb67d9da 100644 --- a/src/components/FollowListButton/FollowListButton.tsx +++ b/src/components/FollowListButton/FollowListButton.tsx @@ -1,7 +1,7 @@ 'use client' import { PROFILE_MSG } from '@/constants' -import { useFollowUser, useModal } from '@/hooks' +import { useModal } from '@/hooks' import { useCurrentUser } from '@/hooks/useCurrentUser' import { fetchGetFollowers, fetchGetFollowing } from '@/services/users/useUsers' import { UserProfileResBody } from '@/types' @@ -13,13 +13,6 @@ const FollowListButton = ({ user }: { user: UserProfileResBody }) => { const myId = currentUser?.memberId const { Modal, isOpen, modalClose, currentModal, handleOpenCurrentModal } = useModal() - const { followingCount, setFollowingCount, followerCount } = useFollowUser({ - memberId: user?.memberId || 0, - isInitFollowing: !!user?.isFollowing, - followingInitCount: user?.followingCount || 0, - followerInitCount: user?.followerCount || 0, - handleOpenCurrentModal, - }) return ( <> @@ -28,7 +21,7 @@ const FollowListButton = ({ user }: { user: UserProfileResBody }) => { onClick={() => { handleOpenCurrentModal('following') }}> - {PROFILE_MSG.FOLLOWING} {followingCount} + {PROFILE_MSG.FOLLOWING} {user?.followingCount}
{PROFILE_MSG.LIST_DIVIDER}
{ onClick={() => { handleOpenCurrentModal('follower') }}> - {PROFILE_MSG.FOLLOWER} {followerCount} + {PROFILE_MSG.FOLLOWER} {user?.followerCount}
{currentModal !== 'login' && isOpen && ( { fetchFn={fetchGetFollowing} myId={myId} type="following" - followingCount={followingCount} - setFollowingCount={setFollowingCount} + followingCount={user?.followingCount} /> )} {currentModal === 'follower' && ( @@ -64,8 +56,7 @@ const FollowListButton = ({ user }: { user: UserProfileResBody }) => { fetchFn={fetchGetFollowers} myId={myId} type="follower" - followingCount={followingCount} - setFollowingCount={setFollowingCount} + followingCount={user?.followingCount} /> )}
diff --git a/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx b/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx index 6a15ec64..a22a701e 100644 --- a/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx +++ b/src/components/PopularLink/PopularLinkList/PopularLinkList.tsx @@ -54,8 +54,8 @@ const PopularLinkList = () => { url={link.url} tagName={link.tagName} tagColor={link.tagColor as ChipColors} - isInitLiked={link.isLiked} - likeInitCount={link.likeCount} + isLiked={link.isLiked} + likeCount={link.likeCount} type="card" /> diff --git a/src/components/ProfileEditButton/ProfileEditButton.tsx b/src/components/ProfileEditButton/ProfileEditButton.tsx index e2ff536d..9f54dd5a 100644 --- a/src/components/ProfileEditButton/ProfileEditButton.tsx +++ b/src/components/ProfileEditButton/ProfileEditButton.tsx @@ -6,52 +6,50 @@ import { UserProfileResBody } from '@/types' import { cls, getProfileButtonColor, getProfileButtonText } from '@/utils' import { useRouter } from 'next/navigation' import Button from '../common/Button/Button' -import DeferredComponent from '../common/DeferedComponent/DeferedComponent' -import Spinner from '../common/Spinner/Spinner' const ProfileEditButton = ({ user }: { user: UserProfileResBody }) => { const router = useRouter() const { currentUser } = useCurrentUser() const myId = currentUser?.memberId const { handleOpenCurrentModal } = useModal() - const { isFollowing, handleClickFollow } = useFollowUser({ - memberId: user?.memberId || 0, - isInitFollowing: !!user?.isFollowing, - followingInitCount: user?.followingCount || 0, - followerInitCount: user?.followerCount || 0, + const { handleClickFollow } = useFollowUser({ + profileId: user?.memberId || 0, + memberId: currentUser?.memberId || 0, + myId: myId || 0, handleOpenCurrentModal, }) - return myId ? ( - - ) : ( - - - + const buttonColor = getProfileButtonColor({ + isFollowing: user?.isFollowing, + memberId: user?.memberId, + myId, + }) + + const buttonText = getProfileButtonText({ + isFollowing: user?.isFollowing, + memberId: user?.memberId, + myId, + }) + + return ( + <> + {user?.memberId && myId && ( + + )} + ) } diff --git a/src/components/common/FollowList/FollowList.tsx b/src/components/common/FollowList/FollowList.tsx index 5fcb9cb7..b8108500 100644 --- a/src/components/common/FollowList/FollowList.tsx +++ b/src/components/common/FollowList/FollowList.tsx @@ -1,4 +1,4 @@ -import { Dispatch, Fragment, SetStateAction } from 'react' +import { Fragment, useEffect } from 'react' import { Spinner } from '@/components' import useFollowQuery from '@/components/common/FollowList/hooks/useFollowQuery' import useInfiniteScroll from '@/hooks/useInfiniteScroll' @@ -12,7 +12,6 @@ export interface FollowListProps { myId?: number type?: string followingCount?: number - setFollowingCount?: Dispatch> } export interface FollowUserProps { @@ -29,7 +28,6 @@ const FollowList = ({ myId, type, followingCount, - setFollowingCount, }: FollowListProps) => { const { followList, fetchNextPage, hasNextPage, isFollowLoading } = useFollowQuery({ @@ -39,35 +37,33 @@ const FollowList = ({ }) const { target } = useInfiniteScroll({ hasNextPage, fetchNextPage }) - return isFollowLoading ? ( - - - - ) : ( - + return ( + <> + {isFollowLoading && } +
    + {followList && + followList.pages.map((group, i) => ( + + {group.responses?.map((user: FollowUserProps) => ( +
  • + +
  • + ))} +
    + ))} +
    +
+ ) } diff --git a/src/components/common/FollowList/hooks/useFollowQuery.ts b/src/components/common/FollowList/hooks/useFollowQuery.ts index 7ca6ada3..3e6942be 100644 --- a/src/components/common/FollowList/hooks/useFollowQuery.ts +++ b/src/components/common/FollowList/hooks/useFollowQuery.ts @@ -5,7 +5,7 @@ import { useInfiniteQuery } from '@tanstack/react-query' const useFollowQuery = ({ memberId, fetchFn, type }: FollowListProps) => { const queryKey = type === 'following' ? QUERY_KEYS.FOLLOWING : QUERY_KEYS.FOLLOWERS - const { data, fetchNextPage, hasNextPage, isLoading } = useInfiniteQuery({ + const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({ queryKey: [queryKey, memberId], queryFn: ({ pageParam }) => fetchFn({ @@ -22,7 +22,7 @@ const useFollowQuery = ({ memberId, fetchFn, type }: FollowListProps) => { followList: data, fetchNextPage, hasNextPage, - isFollowLoading: isLoading, + isFollowLoading: isFetching, } } diff --git a/src/components/common/LinkItem/LinkItem.tsx b/src/components/common/LinkItem/LinkItem.tsx index 2d5d0a39..cbe11507 100644 --- a/src/components/common/LinkItem/LinkItem.tsx +++ b/src/components/common/LinkItem/LinkItem.tsx @@ -44,8 +44,8 @@ export interface LinkItemProps { tagName: string tagColor: ChipColors readUsers?: linkViewHistories[] - isInitLiked?: boolean - likeInitCount: number + isLiked: boolean + likeCount: number read?: boolean summary?: boolean edit?: boolean @@ -62,8 +62,8 @@ const LinkItem = ({ tagName, tagColor, readUsers, - isInitLiked, - likeInitCount, + isLiked, + likeCount, read = false, summary = false, edit = false, @@ -110,11 +110,9 @@ const LinkItem = ({ linkId, }) const { handleSaveReadInfo } = useReadSaveLink({ spaceId, linkId }) - const { isLiked, likeCount, handleClickLike } = useLikeLink({ + const { handleClickLike } = useLikeLink({ spaceId, linkId, - isLikedValue: isInitLiked, - likeCountValue: likeInitCount, }) return ( <> diff --git a/src/components/common/LinkItem/hooks/useLikeLink.ts b/src/components/common/LinkItem/hooks/useLikeLink.ts index 7bb76faa..b4cbe65e 100644 --- a/src/components/common/LinkItem/hooks/useLikeLink.ts +++ b/src/components/common/LinkItem/hooks/useLikeLink.ts @@ -1,57 +1,35 @@ -import { useCallback, useMemo, useState } from 'react' +import { useCallback } from 'react' import { useDeleteLikeLink, usePostLikeLink } from '@/services/link/useLink' -import { debounce } from 'lodash' -import useToggle from '../../Toggle/hooks/useToggle' export interface UseLikeLinkProps { spaceId?: number linkId: number - isLikedValue?: boolean - likeCountValue: number } -const useLikeLink = ({ - linkId, - isLikedValue, - likeCountValue, -}: UseLikeLinkProps) => { - const [isLiked, likeToggle] = useToggle(isLikedValue) - const [likeCount, setLikeCount] = useState(likeCountValue) +const useLikeLink = ({ spaceId, linkId }: UseLikeLinkProps) => { + const { mutate: deleteLikeLink } = useDeleteLikeLink({ spaceId }) + const { mutate: postLikeLink } = usePostLikeLink({ spaceId }) - const { mutate: deleteLikeLink } = useDeleteLikeLink() - const { mutate: postLikeLink } = usePostLikeLink() + const handleRemoveLike = useCallback(() => { + deleteLikeLink({ linkId }) + }, [deleteLikeLink, linkId]) - const debounceUnLikeLink = useMemo( - () => - debounce(async () => { - await deleteLikeLink({ linkId }) - }, 300), - [deleteLikeLink, linkId], - ) - - const debounceLikeLink = useMemo( - () => - debounce(async () => { - await postLikeLink({ linkId }) - }, 300), - [postLikeLink, linkId], - ) + const handleAddLike = useCallback(() => { + postLikeLink({ linkId }) + }, [postLikeLink, linkId]) const handleClickLike = useCallback( (isLike: boolean) => { - likeToggle() if (isLike) { - setLikeCount((prev) => prev - 1) - debounceUnLikeLink() + handleRemoveLike() } else { - setLikeCount((prev) => prev + 1) - debounceLikeLink() + handleAddLike() } }, - [likeToggle, debounceUnLikeLink, debounceLikeLink], + [handleRemoveLike, handleAddLike], ) - return { isLiked, likeCount, handleClickLike } + return { handleClickLike } } export default useLikeLink diff --git a/src/components/common/LinkList/LinkList.tsx b/src/components/common/LinkList/LinkList.tsx index 5a913d29..7a3de0c2 100644 --- a/src/components/common/LinkList/LinkList.tsx +++ b/src/components/common/LinkList/LinkList.tsx @@ -151,8 +151,8 @@ const LinkList = ({ tagName={link.tagName} tagColor={link.tagColor} readUsers={link.linkViewHistories} - isInitLiked={link.isLiked} - likeInitCount={link.likeCount} + isLiked={link.isLiked} + likeCount={link.likeCount} read={read} summary={summary} edit={edit} diff --git a/src/components/common/User/User.tsx b/src/components/common/User/User.tsx index 213207f9..d7be0118 100644 --- a/src/components/common/User/User.tsx +++ b/src/components/common/User/User.tsx @@ -1,6 +1,5 @@ 'use client' -import { Dispatch, SetStateAction } from 'react' import { PROFILE_MSG } from '@/constants' import { useFollowUser, useModal } from '@/hooks' import { cls } from '@/utils' @@ -14,12 +13,11 @@ interface UserProps { nickname: string profileImagePath: string aboutMe?: string - isFollowing?: boolean + isFollowing: boolean isAuth?: boolean followingCount?: number myId?: number profileId?: number - setFollowingCount?: Dispatch> } const User = ({ @@ -29,20 +27,16 @@ const User = ({ aboutMe, isFollowing, isAuth, - followingCount, myId, profileId, - setFollowingCount, }: UserProps) => { const { Modal, isOpen, modalClose, currentModal, handleOpenCurrentModal } = useModal() - const { isFollowing: isFollowingValue, handleClickListInFollow } = - useFollowUser({ - profileId: profileId || 0, - memberId: memberId || 0, - isInitFollowing: !!isFollowing, - followerInitCount: followingCount || 0, - }) + const { handleClickListInFollow } = useFollowUser({ + profileId: profileId || 0, + memberId: memberId || 0, + myId: myId || 0, + }) return ( <> @@ -70,23 +64,16 @@ const User = ({ type="button" className={cls( 'button px-2.5 py-1.5 text-sm', - isFollowingValue ? 'button-white' : 'button-emerald', + isFollowing ? 'button-white' : 'button-emerald', )} onClick={() => { if (myId) { - handleClickListInFollow(isFollowingValue) - if (isFollowingValue) { - profileId === myId && - setFollowingCount?.((prev) => prev! - 1) - } else { - profileId === myId && - setFollowingCount?.((prev) => prev! + 1) - } + handleClickListInFollow(isFollowing) } else { handleOpenCurrentModal('login') } }}> - {isFollowingValue ? PROFILE_MSG.FOLLOWING : PROFILE_MSG.FOLLOW} + {isFollowing ? PROFILE_MSG.FOLLOWING : PROFILE_MSG.FOLLOW} )} diff --git a/src/hooks/useFollowUser.ts b/src/hooks/useFollowUser.ts index 94acef6d..6da30ab3 100644 --- a/src/hooks/useFollowUser.ts +++ b/src/hooks/useFollowUser.ts @@ -1,106 +1,72 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' +import { useCallback } from 'react' import { useDeleteFollow, usePostFollow } from '@/services/users/useUsers' -import { useQueryClient } from '@tanstack/react-query' -import { debounce } from 'lodash' import { useCurrentUser } from './useCurrentUser' export interface UseFollowUserProps { profileId?: number memberId: number - isInitFollowing: boolean - followingInitCount?: number - followerInitCount: number + myId?: number handleOpenCurrentModal?: (current: string) => void } const useFollowUser = ({ profileId, memberId, - isInitFollowing, - followerInitCount, - followingInitCount, + myId, handleOpenCurrentModal, }: UseFollowUserProps) => { - const queryClient = useQueryClient() const { isLoggedIn } = useCurrentUser() - const [isFollowing, setIsFollowing] = useState(isInitFollowing) - const [followingCount, setFollowingCount] = useState(followingInitCount) - const [followerCount, setFollowerCount] = useState(followerInitCount) - const { mutateAsync: postFollow } = usePostFollow(profileId) - const { mutateAsync: deleteFollow } = useDeleteFollow(profileId) + const { mutateAsync: postFollow } = usePostFollow(profileId, myId) + const { mutateAsync: deleteFollow } = useDeleteFollow(profileId, myId) + const targetMemberId = profileId + ? profileId === myId + ? memberId + : profileId + : memberId - useEffect(() => { - setIsFollowing(isInitFollowing) - }, [isInitFollowing]) - - useEffect(() => { - setFollowingCount(followingInitCount) - }, [followingInitCount]) - - useEffect(() => { - setFollowerCount(followerInitCount) - }, [followerInitCount]) - - const debounceUnFollowUser = useMemo( + const handleRemoveFollow = useCallback( () => - debounce(async () => { - if (memberId) { - await deleteFollow({ memberId }) - } - }, 300), - [memberId, deleteFollow], + deleteFollow({ + memberId: targetMemberId, + }), + [targetMemberId, deleteFollow], ) - const debounceFollowUser = useMemo( + const handleAddFollow = useCallback( () => - debounce(async () => { - if (memberId) { - await postFollow({ memberId }) - } - }, 300), - [memberId, postFollow], + postFollow({ + memberId: targetMemberId, + }), + [targetMemberId, postFollow], ) const handleClickFollow = useCallback( (isFollowing: boolean) => { if (isLoggedIn) { - setIsFollowing((prev) => !prev) if (isFollowing) { - setFollowerCount((prev) => prev - 1) - debounceUnFollowUser() + handleRemoveFollow() } else { - setFollowerCount((prev) => prev + 1) - debounceFollowUser() + handleAddFollow() } } else { handleOpenCurrentModal?.('login') } }, - [ - debounceUnFollowUser, - debounceFollowUser, - isLoggedIn, - handleOpenCurrentModal, - ], + [handleRemoveFollow, handleAddFollow, isLoggedIn, handleOpenCurrentModal], ) const handleClickListInFollow = useCallback( (isFollowing: boolean) => { - setIsFollowing((prev) => !prev) if (isFollowing) { - debounceUnFollowUser() + handleRemoveFollow() } else { - debounceFollowUser() + handleAddFollow() } }, - [debounceUnFollowUser, debounceFollowUser], + [handleRemoveFollow, handleAddFollow], ) return { - isFollowing, - followingCount, - setFollowingCount, - followerCount, handleClickFollow, handleClickListInFollow, } diff --git a/src/services/link/useLink.ts b/src/services/link/useLink.ts index f26bf40a..521e9d01 100644 --- a/src/services/link/useLink.ts +++ b/src/services/link/useLink.ts @@ -124,7 +124,9 @@ export const fetchGetPopularLinks = async () => { } // 링크 좋아요 -export const usePostLikeLink = () => { +export const usePostLikeLink = ({ spaceId }: { spaceId?: number }) => { + const queryClient = useQueryClient() + return useMutation({ mutationFn: async (query: ILikeLink['query']) => { const response = await apiClient.post( @@ -133,6 +135,10 @@ export const usePostLikeLink = () => { ) return response }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.POPULAR_LINKS] }) + }, onError: (error: Error) => { console.log(error) }, @@ -140,12 +146,18 @@ export const usePostLikeLink = () => { } // 링크 좋아요 취소 -export const useDeleteLikeLink = () => { +export const useDeleteLikeLink = ({ spaceId }: { spaceId?: number }) => { + const queryClient = useQueryClient() + return useMutation({ mutationFn: async (query: ILikeLink['query']) => { const response = await apiClient.delete(`/api/links/${query.linkId}/like`) return response }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.LINKS, spaceId] }) + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.POPULAR_LINKS] }) + }, onError: (error: Error) => { console.log(error) }, diff --git a/src/services/users/useUsers.ts b/src/services/users/useUsers.ts index ba73c8f9..79308165 100644 --- a/src/services/users/useUsers.ts +++ b/src/services/users/useUsers.ts @@ -71,6 +71,8 @@ export const usePutUserProfile = (memberId: number) => { }, }) } + +// 멤버 프로필 수정 서버 함수 export const fetchPostUserProfile = async ( userId: number, data: RegisterReqBody, @@ -135,7 +137,7 @@ export const fetchGetFollowers = async ({ } // 팔로우 추가 -export const usePostFollow = (profileId?: number) => { +export const usePostFollow = (profileId?: number, myId?: number) => { const queryClient = useQueryClient() return useMutation({ @@ -145,14 +147,28 @@ export const usePostFollow = (profileId?: number) => { }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.MEMBERS, profileId], + queryKey: [QUERY_KEYS.MEMBERS, profileId || myId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWING, profileId || myId], }) queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.FOLLOWING, profileId], + queryKey: [QUERY_KEYS.FOLLOWERS, profileId || myId], }) queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.FOLLOWERS, profileId], + queryKey: [QUERY_KEYS.MEMBERS], }) + if (profileId !== myId) { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS, myId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWING, myId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWERS, myId], + }) + } }, onError: (error: Error) => { console.log(error) @@ -161,7 +177,7 @@ export const usePostFollow = (profileId?: number) => { } // 팔로우 삭제 -export const useDeleteFollow = (profileId?: number) => { +export const useDeleteFollow = (profileId?: number, myId?: number) => { const queryClient = useQueryClient() return useMutation({ @@ -171,14 +187,28 @@ export const useDeleteFollow = (profileId?: number) => { }, onSuccess: () => { queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.MEMBERS, profileId], + queryKey: [QUERY_KEYS.MEMBERS, profileId || myId], }) queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.FOLLOWING, profileId], + queryKey: [QUERY_KEYS.FOLLOWING, profileId || myId], }) queryClient.invalidateQueries({ - queryKey: [QUERY_KEYS.FOLLOWERS, profileId], + queryKey: [QUERY_KEYS.FOLLOWERS, profileId || myId], }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS], + }) + if (profileId !== myId) { + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.MEMBERS, myId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWING, myId], + }) + queryClient.invalidateQueries({ + queryKey: [QUERY_KEYS.FOLLOWERS, myId], + }) + } }, onError: (error: Error) => { console.log(error)