-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] 받은 명함 페이지 - API 연결 로직, 바텀 시트 추가 구현 #149
Conversation
📝 WalkthroughWalkthrough이 PR은 받은 카드와 폴더 관리를 위한 UI, 상태 관리, API 호출 로직을 새로 추가하거나 수정합니다. 주요 변경 사항으로는 받은 카드 데이터를 여러 개로 처리하도록 쿼리 훅 및 컴포넌트 개선, 폴더 데이터를 위한 타입 및 상태 관리(Zustand 스토어) 도입, 모달 및 토스트 컴포넌트 인터페이스 개편, 그리고 폴더와 카드에 대한 생성, 수정, 삭제, 이동을 위한 mutation 훅 추가가 있습니다. Changes
Possibly related PRs
Suggested reviewers
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (34)
src/features/received/model/useDialog.ts (1)
1-18
: 다이얼로그 상태 관리를 위한 훅이 깔끔하게 구현되었습니다.useState를 사용한 간결한 구현과 명확한 함수명(openDialog, closeDialog)으로 가독성이 좋습니다. 하나의 책임만 가지고 있어 재사용성이 높은 훅입니다.
다만, 여러 다이얼로그를 관리해야 하는 경우를 대비해 식별자(id)를 옵션으로 받도록 확장하는 것도 고려해 보세요.
- export function useDialog() { - const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false); + export function useDialog(id?: string) { + const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);src/app/(received)/received/choose/page.tsx (1)
13-34
: 로딩 및 오류 상태 처리 추가 필요현재 컴포넌트는 데이터 로딩 중이거나 오류가 발생했을 때의 UI 처리가 없습니다. 사용자 경험을 개선하기 위해 로딩 상태와 오류 상태에 대한 UI를 추가하는 것이 좋습니다.
function Page() { const { cards: serverReceivedCards, isLoading: isCardsLoading } = useReceivedCardsQuery(null); const handleBack = useHistoryBack(); const { setReceivedCards } = useReceivedCardsStore(); useEffect(() => { if (!isCardsLoading) setReceivedCards(serverReceivedCards); }, [isCardsLoading, serverReceivedCards, setReceivedCards]); + if (isCardsLoading) { + return ( + <div className="flex h-dvh w-full justify-center"> + <div className="flex w-full max-w-[600px] flex-col bg-gray-black"> + <Appbar page="create" onLeftClick={handleBack} /> + <div className="flex h-full items-center justify-center"> + <p className="text-white">로딩 중...</p> + </div> + </div> + </div> + ); + } return ( <div className="flex h-dvh w-full justify-center"> <div className="flex w-full max-w-[600px] flex-col bg-gray-black"> <Appbar page="create" onLeftClick={handleBack} /> <div className="overflow-y-auto px-5 pb-24 scrollbar-hide"> <ChooseReceivedCardView /> </div> <Navbar /> <Toast /> </div> </div> ); }src/features/received/ui/receivedCardList.tsx (2)
16-16
: 에러 및 빈 상태 처리가 필요합니다.현재 로딩 상태는 처리되고 있지만, 에러 발생 시와 데이터가 없을 때의 처리가 빠져 있습니다. 사용자 경험을 향상시키기 위해 이러한 상태들도 처리해 주세요.
- if (isLoading || isFetching) return <p>받은 명함들 로딩중이에요...</p>; // 임시 로딩 구현 + if (isLoading || isFetching) return <p>받은 명함들 로딩중이에요...</p>; // 임시 로딩 구현 + + // 에러 상태 처리 + // const { isError } = useReceivedCardsQuery(selectedFolderId); + // if (isError) return <p>명함을 불러오는 중 오류가 발생했습니다.</p>; + + // 빈 상태 처리 + if (receivedCards.length === 0) return <p>받은 명함이 없습니다.</p>;
13-14
: 쿼리 훅 사용 개선하기
useReceivedCardsQuery
훅에서 반환되는refetch
함수를 활용하여 데이터를 다시 불러오는 기능을 추가할 수 있습니다. 또한 선택한 폴더 ID가 변경될 때 데이터가 올바르게 갱신되는지 확인해 보세요.src/features/received/model/store/useReceivedCardsStore.ts (3)
3-3
: feature 간 결합도 확인
Card
타입을 home 기능에서 가져오는 것은 기능 간의 강한 결합을 만들 수 있습니다. 여러 기능에서 공유되는 타입이라면 shared 디렉토리로 이동하는 것을 고려해 보세요.- import { Card } from '@/features/home/types'; + import { Card } from '@/shared/types';
13-17
: 데이터 유효성 검사 추가스토어 메서드에 입력 유효성 검사를 추가하여 런타임 오류를 방지하는 것이 좋습니다. 또한 카드 상태 변경 시 디버깅을 위한 로깅을 고려해 보세요.
setReceivedCards: (receivedCards) => set({ receivedCards }), deleteCards: (cardIds) => set((state) => ({ - receivedCards: state.receivedCards.filter((receivedCard) => !cardIds.includes(receivedCard.id)), + receivedCards: state.receivedCards.filter((receivedCard) => { + // id가 undefined인 경우 방어 로직 + if (receivedCard.id === undefined) return true; + return !cardIds.includes(receivedCard.id); + }), })),
5-9
: 추가 기능 고려현재 스토어는 카드 목록 설정과 삭제만 지원합니다. 애플리케이션 요구 사항에 따라 단일 카드 추가, 카드 업데이트, 폴더 간 카드 이동 등의 기능을 추가하는 것을 고려해 보세요.
src/shared/ui/Toast/sonner.tsx (1)
18-18
: 스타일 클래스 가독성 개선긴 클래스 문자열은 읽기 어렵고 유지 관리하기 어렵습니다. Tailwind CSS의
@apply
지시문을 사용하거나 별도의 클래스로 분리하는 것을 고려해 보세요. 또한 하드코딩된 마진 값(mb-20
)은 다양한 화면 크기에 적합하지 않을 수 있으므로 반응형 값으로 대체하는 것이 좋습니다.- 'group toast flex justify-center items-center max-w-max group-[.toaster]:bg-gray-600 group-[.toaster]:text-gray-white group-[.toaster]:text-body-4 group-[.toaster]:rounded-full group-[.toaster]:border-none group-[.toaster]:m-auto group-[.toaster]:mb-20', + [ + 'group toast flex justify-center items-center max-w-max', + 'group-[.toaster]:bg-gray-600 group-[.toaster]:text-gray-white group-[.toaster]:text-body-4', + 'group-[.toaster]:rounded-full group-[.toaster]:border-none', + 'group-[.toaster]:m-auto group-[.toaster]:mb-20 md:group-[.toaster]:mb-16 sm:group-[.toaster]:mb-10', + ].join(' '),src/shared/ui/Toast/index.tsx (2)
8-13
: 사용 방법 문서 개선현재 문서에서는
toast.success(message)
또는toast.error(message)
를 사용하라고 안내하고 있지만,toast
객체가 어디서 오는지 명확히 설명되어 있지 않습니다. 문서에 import 예시를 추가하여 명확히 해주세요./** * 공통 컴포넌트 - sonner(toast) * * 사용 방법 : * @example + * import { toast } from 'sonner'; * 토스트 띄울 함수에 toast.success(message) || toast.error(message) * <Toast /> *
24-30
: alt 텍스트 수정 필요성공 아이콘의 alt 텍스트가 "error-icon"으로 설정되어 있어 스크린 리더 사용자에게 혼란을 줄 수 있습니다. 적절한 alt 텍스트로 수정해주세요.
<Image src="/icons/toast-icon/checkIcon.svg" className="text-secondary" - alt="error-icon" + alt="success-icon" width={24} height={24} />src/features/received/model/queries/useReceivedCardsQuery.ts (1)
1-33
: API 연동 로직이 잘 구현되었습니다.폴더 기반으로 카드를 조회하는 기능이 적절하게 구현되었습니다. 몇 가지 개선할 사항이 있습니다:
- 쿼리 키가 고정되어 있어
folderId
가 변경될 때 자동으로 새로운 쿼리가 실행되지 않을 수 있습니다.- 콘솔 로그가 프로덕션 코드에 남아있습니다.
다음과 같이 개선하시는 것을 권장합니다:
export const useReceivedCardsQuery = (folderId: number | null) => { const { data, isLoading, isFetching, isError, refetch } = useQuery({ - queryKey: ['cards'], + queryKey: ['cards', folderId], queryFn: () => _getReceivedCards(folderId), enabled: true, }); return { cards: data?.cards ?? [], isLoading, isFetching, isError, refetch }; };또한 프로덕션에 배포하기 전에 콘솔 로그(16번 줄)를 제거하는 것을 고려해보세요.
src/features/received/ui/foldersList.tsx (1)
1-42
: 폴더 리스트 구현이 깔끔합니다.폴더 선택 기능이 잘 설계되어 있습니다. 다음의 개선 사항을 고려해보세요:
- 배열 인덱스를 키로 사용하는 것은 React에서 권장되지 않습니다.
- 로딩 메시지가 하드코딩되어 있습니다.
{folders.map((folder, index) => { return ( <Tag - key={index} + key={folder.id} size="lg" message={folder.name} className={tagStyle} onClick={() => handleFolderSelect(folder.id)} /> ); })}또한 로딩 상태 처리를 위한 별도의 로딩 컴포넌트를 사용하거나, 다국어 지원을 위한 시스템을 적용하는 것을 고려해보세요.
src/features/received/model/queries/useDeleteFolder.ts (1)
16-27
: 폴더 삭제 뮤테이션 훅이 잘 구현되었습니다.폴더 삭제 기능이 적절하게 구현되었습니다. 다음의 개선 사항을 고려해보세요:
- 주석 처리된 코드가 있습니다.
- 성공/실패 콜백에서 쿼리 무효화나 추가 액션이 없습니다.
export const useDeleteFolder = () => { return useMutation({ - // mutationFn: (folderId: number) => _deleteFolder(folderId), mutationFn: (variables: { folderId: number }) => _deleteFolder(variables.folderId), onSuccess: () => { console.log('폴더 삭제 성공'); + // queryClient.invalidateQueries({ queryKey: ['folders'] }); }, onError: (error) => { console.error('폴더 삭제 실패:', error); }, }); };뮤테이션 성공 후 폴더 목록을 갱신하기 위해
queryClient
를 사용하여 관련 쿼리를 무효화하는 것을 고려해보세요. 이를 위해useQueryClient
를 import하고queryClient
를 선언해야 합니다.src/features/received/model/queries/useMoveCardToFolder.ts (1)
19-30
: 카드 이동 뮤테이션 훅이 잘 구현되었습니다.카드를 폴더로 이동하는 기능이 적절하게 구현되었습니다. 다음의 개선 사항을 고려해보세요:
- 성공/실패 콜백에서 쿼리 무효화나 상태 업데이트가 없습니다.
- 프로덕션 코드에 콘솔 로그가 남아있습니다.
export const useMoveCardToFolder = () => { + const queryClient = useQueryClient(); return useMutation({ mutationFn: (variables: { folderId: number; cardIds: number[] }) => _moveCardToFolder(variables.folderId, variables.cardIds), onSuccess: () => { console.log('받은 명함을 폴더에 저장 성공'); + queryClient.invalidateQueries({ queryKey: ['cards'] }); }, onError: (error) => { console.error('받은 명함을 폴더에 저장 실패 :', error); }, }); };뮤테이션 성공 후 데이터 상태를 갱신하기 위해
queryClient
를 사용하여 관련 쿼리를 무효화하는 것을 고려해보세요. 또한, 프로덕션 환경에서는 콘솔 로그를 제거하거나 로깅 시스템을 사용하는 것이 좋습니다.src/features/received/model/store/useFoldersStore.ts (3)
13-37
: 폴더 ID 생성 로직 개선 필요
addFolder
함수에서 새 폴더의 ID를 계산하는 방식이 잠재적으로 문제를 일으킬 수 있습니다. 현재는 마지막 폴더의 ID + 1로 계산하고 있는데, 폴더가 삭제된 후 새 폴더가 추가될 경우 ID가 중복될 가능성이 있습니다.addFolder: (folderName) => set((state) => { - const lastId = state.folders.length > 0 ? state.folders[state.folders.length - 1].id : 0; + // 기존 ID 중 최대값을 찾아 + 1 하거나, 첫 폴더인 경우 1 사용 + const maxId = state.folders.length > 0 ? Math.max(...state.folders.map(folder => folder.id)) : 0; return { - folders: [...state.folders, { id: lastId + 1, name: folderName }], + folders: [...state.folders, { id: maxId + 1, name: folderName }], }; }),
8-9
: 빈 폴더명 검증 로직 추가 필요현재 폴더 이름에 대한 유효성 검사가 없어 빈 문자열 또는 공백만 있는 이름의 폴더가 생성될 수 있습니다.
- addFolder: (folderName: string) => void; + addFolder: (folderName: string) => { success: boolean; message?: string };그리고 구현부 수정:
addFolder: (folderName) => set((state) => { + // 폴더명 유효성 검사 + if (!folderName.trim()) { + return { ...state, success: false, message: '폴더명을 입력해주세요.' }; + } const lastId = state.folders.length > 0 ? state.folders[state.folders.length - 1].id : 0; return { folders: [...state.folders, { id: lastId + 1, name: folderName }], + success: true, }; }),
23-32
: 폴더 업데이트 성공/실패 상태 반환 필요
updateFolder
함수에서 해당 ID의 폴더를 찾지 못했을 때 상태 변경 없이 그대로 반환하고 있어, 호출자가 업데이트가 성공했는지 실패했는지 알 수 없습니다.- updateFolder: (id: number, newFolderName: string) => void; + updateFolder: (id: number, newFolderName: string) => { success: boolean; message?: string }; updateFolder: (id, newFolderName) => set((state) => { const index = state.folders.findIndex((folder) => folder.id === id); if (index !== -1) { const updatedFolders = [...state.folders]; updatedFolders[index] = { ...updatedFolders[index], name: newFolderName }; - return { folders: updatedFolders }; + return { folders: updatedFolders, success: true }; } - return state; + return { ...state, success: false, message: '폴더를 찾을 수 없습니다.' }; }),src/features/received/model/queries/useDeleteReceivedCard.ts (2)
6-14
: 에러 처리 개선 필요
_deleteReceivedCards
함수 내에서 에러를 캐치하고 다시 던지는 부분이 있는데, 이후useMutation
에서도 같은 에러를 처리하고 있어 중복됩니다. 한 곳에서만 에러를 처리하는 것이 좋습니다.const _deleteReceivedCards = async (cardIds: number[]) => { - try { - const response = await client.delete(`${CLIENT_SIDE_URL}/api/card/receive`, { data: { cardIds } }); - return response; - } catch (error) { - console.error('받은 명함 삭제 실패 :', error); - throw error; - } + return await client.delete(`${CLIENT_SIDE_URL}/api/card/receive`, { data: { cardIds } }); };
19-28
: 사용자 피드백 제공 방식 개선 필요현재 구현에서는 성공이나 실패 시 콘솔에만 로그를 출력하고 있습니다. 사용자에게 적절한 피드백을 제공하도록 개선하면 좋겠습니다.
return useMutation({ mutationFn: (variables: { cardIds: number[] }) => _deleteReceivedCards(variables.cardIds), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['cards'] }); - console.log('받은 명함 삭제 성공'); + // 이미 호출 컴포넌트에서 toast 등을 통해 사용자에게 피드백을 제공하고 있다면 로그는 제거 }, onError: (error) => { - console.error('받은 명함 삭제 실패:', error); + // 에러 발생 시 콜백 함수를 props로 받아 호출하거나 상태를 통해 컴포넌트에 알림 + return error; // 호출 컴포넌트에서 에러 처리 가능하도록 반환 }, });src/features/received/model/queries/useFoldersQuery.ts (3)
11-19
: 불필요한 로그 및 주석 정리 필요함수 내에 "추후 지우기"라는 주석과 함께 콘솔 로그가 있습니다. 프로덕션 코드에는 디버깅용 로그를 남기지 않는 것이 좋습니다.
const _getFolders = async () => { try { const { data } = await client.get<FolderDto>(`${CLIENT_SIDE_URL}/api/card/folders`); - console.log('서버로부터 받은 데이터 : ', data); // 추후 지우기 return data; } catch (error) { console.error(error); throw error; } };
22-31
: 미사용 코드 정리 및 주석 개선 필요현재 코드에는 "추후 수정", "추후 API 호출을 통해 받은 데이터로 수정" 등의 주석이 있고, 주석 처리된 코드도 있습니다. 이는 코드 가독성을 해치므로 정리가 필요합니다.
export const useFoldersQuery = () => { const { data, isLoading, isError } = useQuery({ - // const { data: _ } = useQuery({ - queryKey: ['folders'], // 추후 수정 + queryKey: ['folders'], queryFn: _getFolders, }); - return { folders: data?.folders ?? [], isLoading, isError }; // 추후 API 호출을 통해 받은 데이터로 수정 + return { folders: data?.folders ?? [], isLoading, isError }; - // return { data: FOLDERS_MOCK }; };
9-9
: 미사용 상수 정리 필요
FOLDER_QUERY_KEY
상수가 정의되어 있지만 실제로 사용되지 않고 있습니다. 쿼리 키로['folders']
를 사용하고 있으므로, 이 상수는 제거하거나 실제 사용하는 값으로 변경해야 합니다.-export const FOLDER_QUERY_KEY = 'FOLDER_QUERY_KEY'; +// 상수를 실제로 사용하지 않으므로 제거하거나 아래와 같이 변경 +export const FOLDER_QUERY_KEY = 'folders';그리고 사용 부분 수정:
const { data, isLoading, isError } = useQuery({ - queryKey: ['folders'], + queryKey: [FOLDER_QUERY_KEY], queryFn: _getFolders, });src/features/received/ui/choose/chooseReceivedCardView.tsx (5)
31-34
: 불필요한 콘솔 로그 제거 필요
toggleChecked
함수에 불필요한 디버깅용 콘솔 로그가 남아있습니다. 프로덕션 코드에서는 이러한 로그를 제거하는 것이 좋습니다.const toggleChecked = (id: number) => { setSelectedCardIds((prev) => (prev.includes(id) ? prev.filter((cardId) => cardId !== id) : [...prev, id])); - console.log(selectedCardIds); };
49-54
: 불필요한 콘솔 로그 및 의존성 배열 최적화 필요
useEffect
훅 내에 "asdfasdfa"라는 불필요한 콘솔 로그가 있습니다. 또한,isFetching
을 의존성으로 사용하는 것은 매 요청마다 상태를 초기화할 수 있어 불필요한 리렌더링을 유발할 수 있습니다.useEffect(() => { - if (!isFetching) { + // 초기 로딩이 완료되었을 때만 상태 업데이트 + if (!isLoading && cards.length > 0) { setReceivedCards(cards); } - console.log('asdfasdfa'); -}, [isFetching]); +}, [isLoading, cards]);
56-56
: 로딩 UI 개선 필요현재 로딩 상태는 간단한 텍스트만 표시하고 있습니다. 더 나은 사용자 경험을 위해 로딩 스피너나 스켈레톤 UI를 사용하는 것이 좋습니다.
- if (isFetching) return <p>받은 명함들 로딩중이에요...</p>; // 임시 로딩 구현 + if (isFetching) { + return ( + <div className="flex justify-center items-center h-full w-full py-8"> + <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-gray-900"></div> + <p className="ml-2 text-gray-600">받은 명함들 로딩중이에요...</p> + </div> + ); + }
85-98
: 카드 목록 최적화 필요현재 카드 목록에 대한 최적화가 없어 항목이 많아질 경우 성능 문제가 발생할 수 있습니다. 또한 카드가 없을 때의 처리도 필요합니다.
+ {receivedCards.length === 0 && ( + <div className="flex flex-col items-center justify-center py-8"> + <p className="text-gray-500">받은 명함이 없습니다.</p> + </div> + )} {receivedCards.map((card, index) => { return ( - <div key={index} className="flex max-w-full items-center gap-4"> + <div key={card.id} className="flex max-w-full items-center gap-4"> <ReceivedCheckbox checked={selectedCardIds.includes(card.id)} onCheckedChange={() => toggleChecked(card.id)} /> <div className="min-w-0 flex-1"> - <ReceivedCard key={index} cardData={card} /> + <ReceivedCard cardData={card} /> </div> </div> ); })}
28-28
: isFetching만 사용하는 문제
useReceivedCardsQuery
에서isLoading
대신isFetching
만 사용하고 있습니다.isLoading
은 초기 로딩 상태를,isFetching
은 모든 데이터 페칭 작업에 대해 true가 되므로, 상황에 맞게 적절히 사용해야 합니다.- const { cards, isFetching } = useReceivedCardsQuery(null); + const { cards, isLoading, isFetching } = useReceivedCardsQuery(null);그리고 로딩 표시 부분 수정:
- if (isFetching) return <p>받은 명함들 로딩중이에요...</p>; // 임시 로딩 구현 + if (isLoading) return <p>받은 명함들 로딩중이에요...</p>; // 초기 로딩만 체크src/app/(received)/received/interesting/page.tsx (1)
27-27
: 반복문에서 key로 index 사용을 지양해주세요.
serverReceivedCards.map((value, index) => ...)
사용 시, 데이터 순서가 변경되면 예기치 않은 이슈가 생길 수 있습니다. 가능하다면card.id
같은 고유값을 key로 사용하는 것이 좋습니다.src/features/received/ui/receivedCardView.tsx (4)
26-32
: 상태 분리가 잘 되어 있지만, boolean 플래그가 늘어날 경우 구조 개선을 고려해주세요.
isUpdate
와isAdd
같이 두 개 이상의 상태를 boolean으로 관리할 때 상태가 많이 증가하면 로직이 복잡해질 수 있습니다. 필요한 경우 enum 등을 활용하여 하나의 상태로 관리하는 방안도 고려 가능합니다.
72-82
: 새 폴더 추가 시 사용자 입력값 검증이 필요할 수 있습니다.
현재 빈 문자열 입력을 막는 로직이 없으므로, 폴더 이름이 비어있는 상태로 추가되는 상황을 확인해 주세요. 유효성 검사를 추가해 주시면 좋겠습니다.
96-99
: 디버그용console.log
제거 고려
프로덕션 환경에서 사용하지 않는console.log
는 추후 유지보수 시 제거하는 것이 좋습니다.
118-160
: Modal 내용을 동적으로 구성하는 방식이 직관적입니다.
folders.map((folder, index)
부분에서index
대신folder.id
를 key에 사용하면 좋습니다.- 모달에서 폴더 이름 길이 제한(예: "10/10") 표시는 있지만, 실제 유효성 로직이 없어 사용자가 의도치 않게 제한을 넘을 수 있으니 확인 필요합니다.
src/app/(received)/received/page.tsx (2)
29-31
: 폴더 데이터 로딩 완료 후 스토어 업데이트
isFoldersLoading
가 false일 때 분기 처리를 하는 로직이 좋습니다. 다만, 폴더 데이터가 변경될 때도 감지하려면 의존성 배열에serverFolders
를 넣을지 고려해 보세요.
33-35
: 카드 데이터 로딩 완료 후 스토어 업데이트
폴더 로직과 동일하게isCardsLoading
대신 카드 데이터 변화 여부를 감지하는 방법도 고려해 보세요.
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (25)
src/app/(received)/received/choose/page.tsx
(1 hunks)src/app/(received)/received/interesting/page.tsx
(2 hunks)src/app/(received)/received/page.tsx
(1 hunks)src/entities/folder/dto.ts
(1 hunks)src/entities/folder/types.ts
(1 hunks)src/features/received/config/index.ts
(1 hunks)src/features/received/model/queries/useCreateFolder.ts
(1 hunks)src/features/received/model/queries/useDeleteFolder.ts
(1 hunks)src/features/received/model/queries/useDeleteReceivedCard.ts
(1 hunks)src/features/received/model/queries/useEditFolder.ts
(1 hunks)src/features/received/model/queries/useFoldersQuery.ts
(1 hunks)src/features/received/model/queries/useMoveCardToFolder.ts
(1 hunks)src/features/received/model/queries/useReceivedCardQuery.ts
(0 hunks)src/features/received/model/queries/useReceivedCardsQuery.ts
(1 hunks)src/features/received/model/store/useFoldersStore.ts
(1 hunks)src/features/received/model/store/useReceivedCardsStore.ts
(1 hunks)src/features/received/model/useDialog.ts
(1 hunks)src/features/received/ui/choose/chooseReceivedCardView.tsx
(1 hunks)src/features/received/ui/chooseReceivedCardView.tsx
(0 hunks)src/features/received/ui/foldersList.tsx
(1 hunks)src/features/received/ui/receivedCard.tsx
(1 hunks)src/features/received/ui/receivedCardList.tsx
(1 hunks)src/features/received/ui/receivedCardView.tsx
(3 hunks)src/shared/ui/Toast/index.tsx
(1 hunks)src/shared/ui/Toast/sonner.tsx
(1 hunks)
💤 Files with no reviewable changes (2)
- src/features/received/model/queries/useReceivedCardQuery.ts
- src/features/received/ui/chooseReceivedCardView.tsx
🧰 Additional context used
🧬 Code Definitions (7)
src/entities/folder/dto.ts (1)
src/entities/folder/types.ts (1)
Folder
(1-4)
src/app/(received)/received/choose/page.tsx (2)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(25-33)src/features/received/ui/choose/chooseReceivedCardView.tsx (1)
ChooseReceivedCardView
(20-112)
src/features/received/ui/receivedCardList.tsx (1)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(25-33)
src/features/received/model/store/useFoldersStore.ts (1)
src/entities/folder/types.ts (1)
Folder
(1-4)
src/features/received/ui/choose/chooseReceivedCardView.tsx (4)
src/features/received/model/store/useReceivedCardsStore.ts (1)
useReceivedCardsStore
(11-18)src/features/received/model/queries/useDeleteReceivedCard.ts (1)
useDeleteReceivedCards
(16-29)src/features/received/model/queries/useMoveCardToFolder.ts (1)
useMoveCardToFolder
(19-30)src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(25-33)
src/features/received/ui/foldersList.tsx (1)
src/features/received/model/queries/useFoldersQuery.ts (1)
useFoldersQuery
(22-31)
src/app/(received)/received/interesting/page.tsx (1)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(25-33)
🔇 Additional comments (30)
src/entities/folder/types.ts (1)
1-4
: 간결하고 명확한 타입 정의를 잘 구현하셨습니다.폴더 타입이 필요한 속성만 포함하고 있어 가독성이 좋습니다. ID와 이름은 폴더 관리에 필수적인 속성이며 타입도 적절하게 지정되어 있습니다.
src/entities/folder/dto.ts (1)
1-10
: API 응답 구조가 잘 정의되어 있습니다.API 응답에 필요한 모든 표준 필드(status, message, timestamp)와 데이터 구조가 명확히 정의되어 있습니다. Folder 타입을 재사용하여 일관성을 유지한 점도 좋습니다.
src/features/received/config/index.ts (1)
77-94
:❓ Verification inconclusive
목업 데이터가 잘 구성되어 있으나 프로덕션 코드에서 사용 여부를 확인해주세요.
목업 데이터는 테스트나 개발 환경에서만 사용되어야 합니다. 프로덕션 환경에서는 실제 API 응답을 사용하는지 확인해주세요. 또한 나중에 목업 데이터를 쉽게 찾아 제거할 수 있도록 주석을 추가하는 것이 좋을 것 같습니다.
🏁 Script executed:
#!/bin/bash # Check if the mock data is being used in production code echo "Checking for references to FOLDERS_MOCK in production code..." rg -l "FOLDERS_MOCK" --type ts --type tsx | grep -v "config/index.ts" | grep -v "test" | grep -v "mock"Length of output: 258
프로덕션 코드에서 목업 데이터 사용 여부 재확인 필요
현재 코드에서는 목업 데이터(
FOLDERS_MOCK
)가 잘 구성되어 있습니다. 다만, 해당 데이터가 프로덕션 환경에서 사용되고 있지 않은지 추가 검증이 필요합니다. 아래 사항들을 점검해주세요:
프로덕션 코드 참조 확인:
목업 데이터가 테스트나 개발 환경에서만 사용되어야 하므로, 실제 프로덕션 코드(예:src/features/received/config/index.ts
외 다른 파일)에서FOLDERS_MOCK
을 참조하고 있는지 확인해 주세요.프로덕션 API 응답 사용 여부:
만약 프로덕션 코드에서 해당 목업 데이터를 사용 중이라면, 실제 API 응답으로 대체되어야 합니다.향후 관리 용이성:
목업 데이터임을 명시하는 주석을 추가하여 나중에 쉽게 찾아 제거할 수 있도록 해주세요.검증을 돕기 위해 아래와 같이 수정된 스크립트를 실행해 볼 것을 제안합니다:
#!/bin/bash echo "Searching for references to FOLDERS_MOCK in production code..." rg -l "FOLDERS_MOCK" -g "*.ts" -g "*.tsx" | grep -v "src/features/received/config/index.ts" | grep -v "test" | grep -v "mock"검증 결과에 따라, 코드 수정이나 주석 추가 여부를 결정해 주시기 바랍니다.
src/features/received/ui/receivedCard.tsx (1)
70-70
: 접근성 개선을 위한 적절한 변경alt 속성을 "f"에서 ""로 변경한 것은 접근성 측면에서 적절한 변경입니다. 아바타 이미지는 이미 옆에 사용자 정보가 텍스트로 표시되고 있으므로, 스크린 리더가 중복된 정보를 읽지 않도록 빈 alt 속성을 사용하는 것이 더 좋습니다.
src/shared/ui/Toast/index.tsx (1)
18-18
: 컴포넌트 유연성 감소 확인props와 관련 기능을 제거함으로써 컴포넌트의 유연성이 감소했습니다. 이것이 의도적인 변경인지 확인하고, 필요한 경우 특정 커스터마이징이 필요한 상황에 대비해 일부 props를 다시 추가하는 것을 고려해 보세요.
src/app/(received)/received/interesting/page.tsx (3)
3-3
: useEffect 사용에 대한 추가 import가 적절합니다.
추가된useEffect
import는 컴포넌트 내에서 사이드 이펙트를 처리하기에 적합하며, 코드를 명확하게 유지해 줍니다.
5-6
: 새로운 훅 import 구조가 깔끔합니다.
useReceivedCardsQuery
,useReceivedCardsStore
모두 명확하게 목적과 용도가 드러나며, 모듈화가 잘 된 것으로 보입니다.
15-16
: 쿼리 결과를 의미 있는 변수명으로 파악하기 좋습니다.
serverReceivedCards
라는 명칭은 API에서 수신된 데이터라는 사실을 명확히 표현해 주며,useReceivedCardsStore
의setReceivedCards
와도 연계가 잘 되어 있습니다.src/features/received/ui/receivedCardView.tsx (13)
2-2
: React 훅 사용 준비가 잘 되어 있습니다.
useState
,useEffect
를 함께 사용하는 구조가 명확해 보입니다.
12-15
: 폴더 관련 훅 및 스토어 임포트가 명확합니다.
생성, 삭제, 수정 로직을 분리한 점이 유지보수에 유리해 보입니다.
17-19
: 재사용성 있는 컴포넌트 임포트 구조가 좋습니다.
FoldersList
와ReceivedCardList
로 명확하게 UI를 나눈 점이 가독성을 높여줍니다.
21-24
: 컴포넌트 Props 타입 정의가 명확합니다.
selectedFolderId
와setSelectedFolderId
를 명시해주어 컴포넌트 사용 시 혼동이 적어집니다.
35-39
: 스토어에서 가져온 함수와 서버 mutation을 함께 관리해주는 방식이 합리적입니다.
동일한 동작을 클라이언트 상태와 서버 API 모두에서 처리할 수 있어 데이터 동기화 측면에서 유익합니다.
41-43
: 폴더 선택 로직이 간결합니다.
handleFolderSelect
로selectedFolderId
를 셋팅하는 방식이 뚜렷합니다.
45-47
: 새 폴더 추가 모달을 열기 위한 함수가 명확합니다.
isAdd
상태를 바로 세팅해주어 의도가 한눈에 들어옵니다.
48-51
: 폴더 수정 시 폴더명을 상태에 저장하는 방식
folder.name
을 key로 삼을 경우, 동일한 이름의 폴더가 여러 개 있을 때 의도치 않은 폴더를 수정하게 될 수 있습니다. 폴더 ID를 활용하는 방법도 검토해주세요.
52-57
: 폴더 삭제 로직 구현
서버 요청과 스토어 삭제가 일관성 있게 처리되고, 완료 후toast
로 알림을 주어 UX가 좋습니다.
59-71
: handleUpdateKeyDown 로직의 폴더명 검색 기준 확인 필요
folderName이 중복되면findIndex
로 정확한 폴더를 찾지 못할 가능성이 있으니, ID 기반으로 찾도록 개선이 필요할 수 있습니다.
84-89
: 모달 닫힐 때 상태 초기화는 적절합니다.
isModalOpen
이 닫히면isAdd
와isUpdate
를false
로 돌리는 처리가 간단 명료합니다.
104-104
: FoldersList를 통한 폴더 선택 구조가 일관성 있습니다.
UI 로직과 데이터 로직이 분리되어 있어 확장에 유리해 보입니다.
115-115
: 새로운 ReceivedCardList 컴포넌트와 잘 연동됩니다.
selectedFolderId
를 직접 받아 해당 폴더에 맞는 데이터만 표시할 수 있어, 코드 조직이 깔끔합니다.src/app/(received)/received/page.tsx (9)
3-4
: 라우터 훅과 React 훅의 도입이 적절합니다.
페이지 이동 및 상태 관리를 위해useRouter
,useState
를 함께 사용하는 구조가 효과적으로 보입니다.
6-9
: 쿼리와 스토어를 한 파일에서 임포트하여 일관성이 좋습니다.
useFoldersQuery
,useReceivedCardsQuery
,useFolderStore
,useReceivedCardsStore
등 필요한 모듈을 한눈에 확인 가능해 유지보수에 유리합니다.
19-19
: 폴더 선택 상태 추가
selectedFolderId
상태를 통해 폴더 기반으로 카드를 필터링 가능해, UI가 유연해졌습니다.
20-21
: 멀티 쿼리 사용으로 폴더와 카드 정보를 분리
isCardsLoading
,isFoldersLoading
상태를 활용해 로딩 처리도 명확합니다.
23-23
: 폴더 선택 모달 상태 분리
isChooseModalOpen
을 모달에 넘겨주는 구조가 단순해, 관리하기에 편리해 보입니다.
25-26
: 스토어에 폴더와 명함 데이터를 저장하는 방식
API로부터 받은 값을 redux-like 스토어에 재할당하여 전역 상태 관리가 깔끔해졌습니다.
37-46
: 폴더 선택 시 refetch 로직
폴더 변경 시 마다 데이터를 새로 가져와 동기화하는 것은 유연하지만, 빈번한 refetch 로 인한 퍼포먼스 영향을 확인해 보시는 것이 좋겠습니다.
53-53
: Appbar의 선택 콜백 연결
onRightClickSecond
를 통해 명함 선택 모달을 열 수 있어 사용자 흐름이 분명합니다.
55-55
: ReceivedCardView에 폴더 선택 상태를 넘기는 구조
selectedFolderId
를 하위 컴포넌트에서 직접 사용할 수 있어, 관련 코드가 깔끔하고 예측 가능해집니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useDeleteFolder, useCreateFolder, useEditFolder에서 request body를 전달하는 부분에서 로직이 다 달라요.. 사실 챗지피티의 도움을 받아서,, 각 로직의 차이점이 뭔지 잘 모르겠습니다. 어떨 때 어떤 로직을 쓰는지, 각 request body를 불러오는 로직을 잘 쓴 건지 리뷰해주시면 감사하겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 보통 useDeleteFolder 방식으로 사용할텐데 페칭하는 중에 문제는 없었나요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 가장 추천하는 방법은 라이브러리의 mutationFn의 함수를 확인해보고 타입을 뜯어 보는 방법을 추천합니다.
- 현재 확인해보니 variables 명시적으로 선언해서 payload를 전달하는 방법과, 명시적으로 선언하는것이 아닌 제네릭 방법으로 전달하는 방법 즉 아래 코드로 혼동해서 사용한것으로 보입니다. 각 차이점은 스타일이라고 할수있겠는데요, 현재 적용한 부분에서 문제는 없습니다.
variables를 명시적으로 선언해서 사용
mutationFn: (variables: { folderId: number; name: string }) => _editFolder(variables.folderId, variables.name)
고유 제네릭 사용
mutationFn: (folderName: string) => _createFolder(folderName),
또한 이는 axios 함수에 인자를 전달하는 것에 따라 바뀔수도 있습니다.
개인적인 의견은 저는 제네릭 타입을 사용해서 payload를 그대로 전달해주는 방법을 선호합니다. payload의 필드가 많은 경우 명시적으로 선언해서 사용하는 방법은 비효율적이라 생각합니다.
ex)
const payload = {
id:1,
a:1,
b:1,
c:1,
d:1,
e:1
}
위 처럼 페이로드로 전달해주여야 하는 값이 많은 경우 명시적으로 선언할때, 코드를 인용하자면 아래와 같이 사용해야합니다.
const _deleteFolder = async (id, a, b, c, d, e) => {
try {
const response = await client.delete(`${CLIENT_SIDE_URL}/api/card/folder`, { data: { id, a, b, c, d, e } });
return response;
} catch (error) {
console.error('폴더 삭제 실패 :', error);
throw error;
}
};
export const useDeleteFolder = () => {
return useMutation({
// mutationFn: (folderId: number) => _deleteFolder(folderId),
mutationFn: (variables: { id, a,b,c,d,e }) => _deleteFolder(variables.id, variables.a,variables.b,variables.c,variables.d,variables.e),
onSuccess: () => {
console.log('폴더 삭제 성공');
},
onError: (error) => {
console.error('폴더 삭제 실패:', error);
},
});
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@semnil5202 넵 페칭하는 데에는 다른 문제 발생은 없었습니다..!
@jangwonyoon 확실히 쿼리와 뮤테이션에 대해 공부할 필요를 많이 느끼네요..ㅜ 알려주셔서 감사합니다 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (9)
src/features/received/ui/foldersList.tsx (2)
20-20
: 임시 로딩 텍스트 개선이 필요합니다.로딩 상태 UI는 임시로 구현된 것으로 보입니다. 실제 배포 전에 로딩 스피너나 스켈레톤과 같은 적절한 UI 컴포넌트로 대체하는 것이 좋겠습니다.
14-42
: 폴더 목록이 비어있는 경우를 처리해주세요.현재 코드는 폴더 목록이 비어있는 경우에 대한 처리가 없습니다. 폴더가 없을 때 사용자에게 안내 메시지를 보여주는 것이 UX 측면에서 좋습니다.
if (isLoading || isFetching) return <p>폴더 로딩중이에요...</p>; // 임시 로딩 구현 + if (folders.length === 0) return <p>폴더가 없습니다. 첫 폴더를 만들어보세요!</p>; return ( ... );
src/features/received/model/queries/useDeleteFolder.ts (3)
6-14
: 에러 로깅 중복을 제거하세요.현재
_deleteFolder
함수와useDeleteFolder
훅 모두에서 동일한 에러를 로깅하고 있습니다. 에러 로깅은 한 곳에서만 처리하는 것이 좋습니다.const _deleteFolder = async (folderId: number) => { try { const response = await client.delete(`${CLIENT_SIDE_URL}/api/card/folder`, { data: { folderId } }); return response; } catch (error) { - console.error('폴더 삭제 실패 :', error); throw error; } };
22-22
: 프로덕션 코드에서 불필요한 console.log를 제거하세요.프로덕션 환경에서는 디버깅용 console.log를 제거하는 것이 좋습니다. 로깅이 필요한 경우 더 체계적인 로깅 시스템을 사용하세요.
onSuccess: () => { - console.log('폴더 삭제 성공'); queryClient.invalidateQueries({ queryKey: ['folders'] }); },
19-28
: Toast 메시지를 추가하세요.폴더 삭제 성공/실패 시 사용자에게 Toast 메시지로 알려주는 것이 좋은 UX 입니다. PR에 Toast 컴포넌트가 이미 있는 것으로 보이니 활용해보세요.
export const useDeleteFolder = () => { const queryClient = useQueryClient(); + const { showToast } = useToast(); // Toast 컴포넌트 hook 추가 필요 return useMutation({ mutationFn: (variables: { folderId: number }) => _deleteFolder(variables.folderId), onSuccess: () => { console.log('폴더 삭제 성공'); queryClient.invalidateQueries({ queryKey: ['folders'] }); + showToast('폴더가 삭제되었습니다.'); }, onError: (error) => { console.error('폴더 삭제 실패:', error); + showToast('폴더 삭제에 실패했습니다. 다시 시도해주세요.'); }, }); };src/features/received/model/queries/useFoldersQuery.ts (2)
10-10
: 불필요한 console.log를 제거하세요.주석에도 언급되어 있듯이 '추후 지우기'로 표시된 console.log가 있습니다. 이런 디버깅용 코드는 PR 병합 전에 제거하는 것이 좋습니다.
try { const { data } = await client.get<FolderDto>(`${CLIENT_SIDE_URL}/api/card/folders`); - console.log('서버로부터 받은 데이터 : ', data); // 추후 지우기 return data; } catch (error) {
18-25
: useFoldersQuery 리팩토링 제안현재 구현은 이미 개선되었지만, 더 간결하게 만들 수 있습니다. 특히 폴더 데이터가 없을 때의 기본값 처리와 쿼리 옵션을 더 명확하게 할 수 있습니다.
export const useFoldersQuery = () => { const { data, isLoading, isFetching, isError, refetch } = useQuery({ queryKey: ['folders'], queryFn: _getFolders, enabled: true, + staleTime: 1000 * 60 * 5, // 5분 동안 데이터 캐싱 + retry: 1, // 실패 시 한 번만 재시도 }); return { folders: data?.folders ?? [], isLoading, isFetching, isError, refetch }; };src/app/(received)/received/page.tsx (2)
34-34
: 불필요한 console.log를 제거하세요.디버깅용 console.log는 PR 병합 전에 제거하는 것이 좋습니다.
useEffect(() => { if (!isFoldersLoading) setFolders(serverFolders); - console.log('선택한 폴더 : ', selectedFolderId); }, [isFoldersLoading]);
41-50
:❓ Verification inconclusive
폴더 선택 시 카드 데이터 refetch 최적화
모든 폴더 선택에서 API 호출이 발생하는 것은 좋습니다만, 이미 React Query가
selectedFolderId
가 변경될 때 자동으로 refetch 하도록 설정되어 있습니다. 불필요한 추가 refetch가 발생할 수 있습니다.
useReceivedCardsQuery
훅에selectedFolderId
가 의존성으로 올바르게 설정되어 있는지 확인해 주세요. 이 값이 변경되면 자동으로 쿼리가 다시 실행되어야 합니다.
🏁 Script executed:
#!/bin/bash # useReceivedCardsQuery 훅의 구현을 확인합니다. rg -A 5 "useReceivedCardsQuery" --type ts --type tsxLength of output: 80
리팩토링 요청: useReceivedCardsQuery 훅의 의존성 배열 재검증 필요
현재 페이지의 useEffect에서 별도로 API를 호출해 카드를 refetch 하고 있지만, React Query가 제공하는 useReceivedCardsQuery 훅이 selectedFolderId 변경 시 자동으로 쿼리를 재실행하도록 구성되어 있다면 중복 refetch가 발생할 수 있습니다.
따라서 아래 사항을 확인해 주세요:
- useReceivedCardsQuery 훅 내에 selectedFolderId가 의존성 배열에 포함되어 있는지
→ 만약 누락되어 있다면, selectedFolderId가 변경될 때 자동 refetch가 안 되어 추가로 API 호출이 발생할 수 있습니다.- 관련 구현을 다시 검토하시고, 필요 시 해당 의존성을 추가하는 방식으로 리팩토링 해주시기 바랍니다.
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/app/(received)/received/page.tsx
(1 hunks)src/features/received/model/queries/useCreateFolder.ts
(1 hunks)src/features/received/model/queries/useDeleteFolder.ts
(1 hunks)src/features/received/model/queries/useEditFolder.ts
(1 hunks)src/features/received/model/queries/useFoldersQuery.ts
(1 hunks)src/features/received/ui/foldersList.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/features/received/model/queries/useEditFolder.ts
- src/features/received/model/queries/useCreateFolder.ts
🧰 Additional context used
🧬 Code Definitions (1)
src/features/received/ui/foldersList.tsx (1)
src/features/received/model/queries/useFoldersQuery.ts (1)
useFoldersQuery
(18-25)
🔇 Additional comments (1)
src/features/received/model/queries/useDeleteFolder.ts (1)
1-29
:❓ Verification inconclusive
mutation 함수의 요청 본문(request body) 전달 방식이 일관적이지 않습니다.
이전 리뷰 코멘트에서 언급된 대로, 각 mutation 함수마다 요청 본문을 전달하는 방식이 다릅니다. 이 파일에서는
data: { folderId }
형태로 전달하고 있는데, 다른 파일들과 일관성 있게 통일하는 것이 좋겠습니다.
🌐 Web query:
axios HTTP 요청 본문을 DELETE 메소드에서 어떻게 전달하나요?
💡 Result:
Axios를 사용하여 DELETE 메서드로 HTTP 요청 본문을 전달하려면
config
객체의data
속성을 활용해야 합니다. DELETE 요청은 POST나 PUT과 달리 기본적으로 두 번째 파라미터로 데이터를 직접 전달하지 않으며, 명시적으로data
속성에 객체를 할당해야 서버에서req.body
로 접근 가능합니다.1. 기본 방법:
axios.delete()
활용
axios.delete()
메서드의 두 번째 인자로data
속성을 포함한 설정 객체를 전달합니다.axios.delete('/api/resource', { data: { id: 123, password: 'securePassword123' }, headers: { 'Content-Type': 'application/json' } }) .then(response => console.log(response)) .catch(error => console.error(error));
- 주의사항: 서버 측에서
req.body
파싱을 위해 body-parser 미들웨어가 구성되어 있어야 합니다[1][4][10].2. 확장 방법: 일반 Axios API 사용
method
속성을 명시적으로 지정하는 방식으로도 구현 가능합니다.axios({ method: 'delete', url: '/api/resource', data: { id: 456, token: 'abcde-xyz' } }) .then(response => console.log(response)) .catch(error => console.error(error));주요 차이점 비교
방법 구문 예시 사용 사례 axios.delete()
명시적 DELETE 메서드 호출 단순한 DELETE 요청 일반 Axios API 유연한 설정 객체 사용 복잡한 요청 구성이 필요할 때 HTTP 표준에서는 DELETE 메서드에 본문 사용을 권장하지 않지만, Axios는
data
속성을 통해 본문 전송을 지원합니다[13][16]. 단, 서버 측 라우트에서 DELETE 요청의 본문을 처리할 수 있도록 구현해야 합니다 (Express 예시:app.delete('/path', (req, res) => { ... })
).실무에서는 RESTful 원칙에 따라 리소스 식별자를 URL 경로나 쿼리 파라미터로 전달하는 것이 일반적이지만, 민감한 데이터 전송 시 본문 사용이 유용할 수 있습니다[6][13].
Citations:
- 1: https://velog.io/@bigbrothershin/Axios-delete-%EC%9A%94%EC%B2%AD-%EC%8B%9C-body%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%84%A3%EB%8A%94-%EB%B2%95
- 2: https://sumni.tistory.com/152
- 3: https://developer-haru.tistory.com/50
- 4: https://www.soyou.space/8603906d-a1c7-4093-84e3-30ba3d9f663f
- 5: https://blog.naver.com/sk_0021/222718571266
- 6: https://sw-ing.tistory.com/170
- 7: https://apidog.com/kr/blog/http-request-axios-3/
- 8: https://inpa.tistory.com/entry/AXIOS-%F0%9F%93%9A-%EC%84%A4%EC%B9%98-%EC%82%AC%EC%9A%A9
- 9: https://apidog.com/kr/blog/how-to-make-rest-api-calls-with-axios-2/
- 10: https://velog.io/@ssena/Axios-delete-%EC%9A%94%EC%B2%AD-%EC%8B%9C-body%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%8B%B4%EA%B8%B0%EC%A7%80-%EC%95%8A%EB%8A%94-%EB%AC%B8%EC%A0%9C%EC%99%80-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95
- 11: https://wonit.tistory.com/305
- 12: https://apidog.com/kr/blog/react-axios-post-3/
- 13: https://4sii.tistory.com/125
- 14: https://tuhbm.github.io/2019/03/21/axios/
- 15: https://velog.io/@qkrcksdnr98/axios-delete-method
- 16: https://yeonjae001110.tistory.com/108
- 17: https://conding-note.tistory.com/301
- 18: https://oranthy.tistory.com/569
- 19: https://choihaneul9545.tistory.com/42
- 20: https://velog.io/@eeeyooon/Axios%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
일관성 체크 필요 – DELETE 요청 본문 전달 방식 통일
현재
useDeleteFolder
훅 내의_deleteFolder
함수는 axios의 DELETE 메서드 요구사항에 맞게{ data: { folderId } }
형태로 요청 본문을 전달하고 있어 기술적으로는 올바른 구현입니다. 다만, 다른 mutation 함수들과 전달 방식이 상이한 것으로 보이므로 전체 코드베이스 내에서 요청 본문 전달 방식의 통일성을 검토 및 수정할 필요가 있습니다.
- 확인 사항
- 다른 mutation 함수들이 DELETE 혹은 기타 메서드에서 요청 본문을 전달하는 방식이 어떻게 구현되어 있는지 검토
- 내부 컨벤션에 따라 수정할지, 아니면 전역적으로 기존 방식(axios의
data
속성 사용)을 채택할지 결정이 부분은 다른 파일과의 일관성을 고려하여 추가 검토 후 통일된 구현으로 수정해 주시기 바랍니다.
const { isLoading, isFetching } = useFoldersQuery(); | ||
const { folders } = useFolderStore(); | ||
|
||
if (isLoading || isFetching) return <p>폴더 로딩중이에요...</p>; // 임시 로딩 구현 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
API 오류 처리가 필요합니다.
현재 폴더 데이터를 가져오는 과정에서 API 오류가 발생할 경우에 대한 처리가 없습니다. isError
상태를 확인하여 오류 메시지를 표시하는 로직을 추가하는 것이 좋습니다.
- const { isLoading, isFetching } = useFoldersQuery();
+ const { isLoading, isFetching, isError } = useFoldersQuery();
const { folders } = useFolderStore();
if (isLoading || isFetching) return <p>폴더 로딩중이에요...</p>; // 임시 로딩 구현
+ if (isError) return <p>폴더를 불러오는 데 실패했습니다. 다시 시도해주세요.</p>;
return (
...
);
📝 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.
const { isLoading, isFetching } = useFoldersQuery(); | |
const { folders } = useFolderStore(); | |
if (isLoading || isFetching) return <p>폴더 로딩중이에요...</p>; // 임시 로딩 구현 | |
const { isLoading, isFetching, isError } = useFoldersQuery(); | |
const { folders } = useFolderStore(); | |
if (isLoading || isFetching) return <p>폴더 로딩중이에요...</p>; // 임시 로딩 구현 | |
if (isError) return <p>폴더를 불러오는 데 실패했습니다. 다시 시도해주세요.</p>; | |
return ( | |
... | |
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
고생많으셨습니다. 쿼리 다루는 부분에서 잘못 작성된 부분이 좀 있어서 코멘트 남겼습니다.
이 부분 확인해주시고 수정 후에 머지하면 좋을 것 같아요.
#149 (comment) 이 코멘트와 #149 (comment) 이 코멘트가 서로 연관이 있어보입니다.
useEffect(() => { | ||
if (!isFetching) { | ||
setReceivedCards(cards); | ||
} | ||
console.log('asdfasdfa'); | ||
}, [isFetching]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
음 react query로 받아온 서버상태 (cards)를 클라이언트 상태 (selectedCardIds)로 바꿀 이유가 딱히 없어보이는데, 이런 로직이 추가된 배경이 있을까요?
콘솔도 지워주세요~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@semnil5202 이 부분은 @youngju6143 의견을 들어봐야겠지만 전역상태로 관리하고 싶어서 사용한게 아닌가 싶습니다.
그것과 별개로 isFetching에 플래그를 두어 할당한 부분이 어색해 보입니다. 실제 데이터가 있는경우 즉 data가 undefined가 아닌 경우에 할당이 되도록 다루는게 좋아보입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@semnil5202 폴더를 이동할 때, 여러 개의 카드 ID를 선택해야 해서 클라이언트 상태에서 selectedCardIds 배열을 관리하고 있습니다.
이후, 폴더를 이동하거나 삭제할 때 이 배열을 request body로 전달하는 방식으로 구현했습니다.
현재 로직이 가장 적절하다고 판단했는데, 혹시 더 나은 방법이 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@youngju6143 고생하셨습니다. 몇 가지 코멘트를 남겨두었습니다~
const { data, isLoading, isFetching, isError, refetch } = useQuery({ | ||
queryKey: ['cards'], | ||
queryFn: () => _getReceivedCards(folderId), | ||
enabled: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enabled
의 값은 default값이 true입니다. 현재 상황에서는 folderId 값이 존재하는 경우에만 사용하도록
enabled: !!folderId
로 사용하는것이 맞아보입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
folderId 가 초반에 undefined여서 fetching 에러가 나기때문에 위와같이 작업합니다. Dependecy Query 키워드로 검색해보면 관련 내용을 볼 수 있습니다.
useEffect(() => { | ||
if (!isFetching) { | ||
setReceivedCards(cards); | ||
} | ||
console.log('asdfasdfa'); | ||
}, [isFetching]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@semnil5202 이 부분은 @youngju6143 의견을 들어봐야겠지만 전역상태로 관리하고 싶어서 사용한게 아닌가 싶습니다.
그것과 별개로 isFetching에 플래그를 두어 할당한 부분이 어색해 보입니다. 실제 데이터가 있는경우 즉 data가 undefined가 아닌 경우에 할당이 되도록 다루는게 좋아보입니다.
@youngju6143 너무 고생하셨습니다~ 👍 👍 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (9)
src/features/received/model/mutations/useDeleteReceivedCard.ts (3)
8-16
: 에러 처리가 중복되어 있습니다.
_deleteReceivedCards
함수와useMutation
의onError
콜백 모두에서 에러 로깅이 이루어지고 있습니다. 더 깔끔한 코드를 위해 한 곳에서만 로깅하는 것을 고려해보세요.const _deleteReceivedCards = async (cardIds: number[]) => { try { const response = await client.delete(`${CLIENT_SIDE_URL}/api/card/receive`, { data: { cardIds } }); return response; } catch (error) { - console.error('받은 명함 삭제 실패 :', error); throw error; } };
21-30
: useMutation 구성이 적절합니다만, 사용자 피드백을 위한 개선이 필요합니다.mutation 함수와 콜백이 잘 구성되어 있지만, 사용자에게 성공 또는 실패 메시지를 표시하는 방법이 없습니다. 토스트 메시지나 알림을 통해 사용자에게 작업 결과를 알려주는 것이 좋습니다.
export const useDeleteReceivedCards = (folderId: number | null) => { const queryClient = useQueryClient(); + const { showToast } = useToast(); // 토스트 훅 추가 return useMutation({ mutationFn: (variables: { cardIds: number[] }) => _deleteReceivedCards(variables.cardIds), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [CARD_QUERY_KEY, folderId] }); - console.log('받은 명함 삭제 성공'); + showToast({ message: '명함이 성공적으로 삭제되었습니다.', type: 'success' }); }, onError: (error) => { console.error('받은 명함 삭제 실패:', error); + showToast({ message: '명함 삭제에 실패했습니다. 다시 시도해주세요.', type: 'error' }); }, }); };
1-32
: 전체적으로 코드가 잘 구성되어 있습니다.이 훅은 받은 명함 삭제 기능을 효과적으로 구현하고 있습니다. React Query를 활용한 데이터 관리가 적절하게 이루어지고 있으며, API 호출 및 쿼리 무효화 로직이 깔끔하게 정리되어 있습니다.
다음과 같은 작은 개선을 고려해볼 수 있습니다:
- 응답 및 에러에 대한 타입 정의 추가
- 성공/실패 시 사용자 피드백 메커니즘 추가
cardIds
배열의 유효성 검사 (예: 빈 배열 체크)src/features/received/model/queries/useFoldersQuery.ts (1)
12-12
: 개발 완료 후 console.log 제거 필요추후 제거하겠다고 표시해두셨으나, 프로덕션 환경에 배포하기 전에 콘솔 로그를 반드시 제거해주세요. 디버깅용 로그는 개발 중에는 유용하지만 프로덕션에서는 성능 이슈와 보안 위험을 초래할 수 있습니다.
src/features/received/model/mutations/useEditFolder.ts (1)
10-13
: ES6 객체 속성 단축 구문 적용 권장객체 속성 이름과 변수 이름이 동일할 때는 ES6의 단축 구문을 사용하면 코드를 더 간결하게 작성할 수 있습니다:
- const response = await client.put(`${CLIENT_SIDE_URL}/api/card/folder`, { - folderId: folderId, - name: name, - }); + const response = await client.put(`${CLIENT_SIDE_URL}/api/card/folder`, { + folderId, + name, + });이렇게 하면 코드가 더 간결해지고 가독성이 향상됩니다.
src/features/received/model/mutations/useCreateFolder.ts (2)
25-28
: 콘솔 로그 제거 필요성공 메시지를 콘솔에 출력하는 것은 개발 중에는 유용하지만, 프로덕션에 배포하기 전에는 제거하거나 적절한 로깅 시스템으로 대체하는 것이 좋습니다. 또는 사용자에게 토스트 메시지나 알림을 표시하는 방식으로 변경하는 것이 좋습니다.
onSuccess: () => { - console.log('폴더 생성 성공'); queryClient.invalidateQueries({ queryKey: [FOLDER_QUERY_KEY] }); + // 필요하다면 토스트 메시지 표시 },
1-33
: 유사한 mutation 파일들에 대한 리팩토링 고려이 파일과 다른 mutation 파일들(useEditFolder, useDeleteFolder)이 매우 유사한 패턴을 가지고 있습니다. 향후 유지보수를 위해 이러한 패턴을 추상화하여 중복을 줄이는 것을 고려해보세요. 예를 들어, 공통 mutation 생성 함수를 만들고 각 액션에 맞게 구성할 수 있습니다:
// 예시: createMutation.ts import { useMutation, useQueryClient } from '@tanstack/react-query'; import { client } from '@/shared/apis/client'; import { CLIENT_SIDE_URL } from '@/shared/constants'; export const createMutation = ({ mutationFn, queryKey, successMessage, errorMessage, }) => { const queryClient = useQueryClient(); return useMutation({ mutationFn, onSuccess: () => { console.log(successMessage); queryClient.invalidateQueries({ queryKey }); }, onError: (error) => { console.error(errorMessage, error); }, }); };이런 식으로 중복 코드를 줄이고 유지보수성을 높일 수 있습니다.
src/features/received/model/mutations/useMoveCardToFolder.ts (2)
6-17
: API 요청 함수가 잘 구현되어 있지만, 오류 처리를 개선할 수 있습니다.
_moveCardToFolder
함수는 기본적인 구조가 잘 잡혀 있습니다. 하지만 다음과 같은 개선점이 있습니다:
- 오류 처리가 모든 오류 유형에 대해 동일하게 처리되고 있습니다. API 응답 코드나 네트워크 오류 등 다양한 상황에 맞게 처리하는 것이 좋습니다.
- 콘솔 로그는 개발 환경에서만 표시되도록 조건부로 처리하는 것이 좋습니다.
const _moveCardToFolder = async (folderId: number, cardIds: number[]) => { try { const response = await client.put(`${CLIENT_SIDE_URL}/api/card/receive/folder`, { folderId: folderId, cardIds: cardIds, }); return response; } catch (error) { - console.error('받은 명함을 폴더에 저장 실패 :', error); + // 개발 환경에서만 로깅 + if (process.env.NODE_ENV !== 'production') { + console.error('받은 명함을 폴더에 저장 실패 :', error); + } + // 오류 타입에 따른 처리 + if (error instanceof Error) { + // 구체적인 오류 처리 + } throw error; } };
1-30
: 타입 안전성 및 코드 구조 개선 제안전체적으로 코드가 잘 작성되어 있지만, 다음과 같은 개선 사항을 고려해 보세요:
- API 응답 타입을 명시적으로 정의하면 타입 안전성이 향상됩니다.
- 변수를 객체 구조 분해 할당으로 전달하면 가독성이 향상될 수 있습니다.
- 재사용 가능한 에러 처리 유틸리티를 사용하는 것이 좋습니다.
import { useMutation } from '@tanstack/react-query'; import { client } from '@/shared/apis/client'; import { CLIENT_SIDE_URL } from '@/shared/constants'; + import { queryClient } from '@/shared/apis/queryClient'; + import { QUERY_KEYS } from '@/shared/constants/queryKeys'; + import { handleApiError } from '@/shared/utils/errorHandling'; + import { toast } from '@/shared/components/toast'; + // API 응답 타입 정의 + interface MoveCardResponse { + success: boolean; + message?: string; + } - const _moveCardToFolder = async (folderId: number, cardIds: number[]) => { + const _moveCardToFolder = async ({ folderId, cardIds }: { folderId: number; cardIds: number[] }): Promise<MoveCardResponse> => { try { const response = await client.put<MoveCardResponse>(`${CLIENT_SIDE_URL}/api/card/receive/folder`, { folderId, cardIds, }); return response; } catch (error) { - console.error('받은 명함을 폴더에 저장 실패 :', error); + handleApiError(error, '받은 명함을 폴더에 저장 실패'); throw error; } }; export const useMoveCardToFolder = () => { return useMutation({ - mutationFn: (variables: { folderId: number; cardIds: number[] }) => - _moveCardToFolder(variables.folderId, variables.cardIds), + mutationFn: (variables: { folderId: number; cardIds: number[] }) => _moveCardToFolder(variables), onSuccess: () => { - console.log('받은 명함을 폴더에 저장 성공'); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.RECEIVED_CARDS] }); + queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.FOLDERS] }); + toast.success('명함이 성공적으로 이동되었습니다.'); }, onError: (error) => { - console.error('받은 명함을 폴더에 저장 실패 :', error); + toast.error('명함 이동 중 오류가 발생했습니다.'); }, }); };
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
src/features/received/model/mutations/useCreateFolder.ts
(1 hunks)src/features/received/model/mutations/useDeleteFolder.ts
(1 hunks)src/features/received/model/mutations/useDeleteReceivedCard.ts
(1 hunks)src/features/received/model/mutations/useEditFolder.ts
(1 hunks)src/features/received/model/mutations/useMoveCardToFolder.ts
(1 hunks)src/features/received/model/queries/useFoldersQuery.ts
(1 hunks)src/features/received/model/queries/useReceivedCardsQuery.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/features/received/model/queries/useReceivedCardsQuery.ts
🧰 Additional context used
🧬 Code Definitions (4)
src/features/received/model/mutations/useDeleteFolder.ts (1)
src/features/received/model/queries/useFoldersQuery.ts (1)
FOLDER_QUERY_KEY
(7-7)
src/features/received/model/mutations/useDeleteReceivedCard.ts (1)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
CARD_QUERY_KEY
(7-7)
src/features/received/model/mutations/useEditFolder.ts (1)
src/features/received/model/queries/useFoldersQuery.ts (1)
FOLDER_QUERY_KEY
(7-7)
src/features/received/model/mutations/useCreateFolder.ts (1)
src/features/received/model/queries/useFoldersQuery.ts (1)
FOLDER_QUERY_KEY
(7-7)
🔇 Additional comments (13)
src/features/received/model/mutations/useDeleteReceivedCard.ts (5)
1-6
: import 문이 효율적으로 구성되어 있습니다.필요한 의존성이 적절하게 가져와져 있으며,
CARD_QUERY_KEY
를 활용하여 쿼리 캐시 무효화를 위한 준비가 잘 되어 있습니다.
10-10
: API 요청 방식이 적절합니다.DELETE 요청에 request body를 포함시키는 방식이 올바르게 구현되어 있습니다.
{ data: { cardIds } }
형태로 데이터를 전달하는 것은 axios에서 DELETE 요청에 body를 포함할 때 권장되는 방식입니다.
18-20
: folderId의 타입과 역할이 명확합니다.
folderId
가number | null
타입으로 정의되어 있어, 특정 폴더 또는 모든 폴더에 대한 쿼리 무효화를 처리할 수 있습니다.
24-24
: 쿼리 무효화 전략이 적절합니다.특정 폴더의 명함 데이터를 삭제한 후, 해당 폴더의 쿼리만 무효화하는 전략이 효율적입니다. 불필요한 데이터 리페칭을 방지하고 성능을 최적화합니다.
22-22
: mutationFn의 인터페이스가 명확합니다.
variables
객체를 통해cardIds
배열을 받아 내부 함수로 전달하는 패턴이 잘 구현되어 있습니다. 사용 시 타입 안전성을 제공합니다.src/features/received/model/queries/useFoldersQuery.ts (3)
21-24
: 쿼리 키 관리 개선 권장이전 리뷰에서 언급된 것처럼, 쿼리 키를 상수로 관리하는 것이 좋습니다. 현재는 단일 상수가 있지만, 더 구조화된 방식으로 관리하면 invalidation 시 일관성을 유지하기 쉽습니다.
-export const FOLDER_QUERY_KEY = 'FOLDER_QUERY_KEY'; +export const QUERY_KEYS = { + FOLDERS: 'folders', +} as const;그리고 사용할 때:
-queryKey: [FOLDER_QUERY_KEY], +queryKey: [QUERY_KEYS.FOLDERS],이렇게 하면 추후 다른 쿼리 키를 추가할 때 더 일관된 구조를 유지할 수 있습니다.
9-18
: 에러 처리와 구조화가 잘 되어 있습니다비동기 함수에 try-catch 블록을 추가하여 에러 처리를 개선한 점이 좋습니다. 에러를 적절히 기록하고 다시 throw하여 상위 컴포넌트에서 처리할 수 있게 한 부분도 좋은 패턴입니다.
26-26
: 빈 배열 기본값 설정이 잘 되어 있습니다
folders: data?.folders ?? []
와 같이 옵셔널 체이닝과 nullish 병합 연산자를 사용하여 기본값을 빈 배열로 설정한 것은 좋은 방식입니다. 이를 통해 데이터가 없는 경우에도 컴포넌트가 오류 없이 렌더링될 수 있습니다.src/features/received/model/mutations/useDeleteFolder.ts (2)
8-16
: DELETE 요청의 구현 방식에 대한 참고사항이 구현은 잘 작동하지만, REST 관행에 따르면 일반적으로 DELETE 요청은 요청 본문(body)보다는 URL 파라미터를 통해 ID를 전달합니다 (예:
/api/card/folder/{folderId}
). 현재 방식은 백엔드 요구사항에 맞춘 것으로 보이므로 작동에는 문제가 없지만, REST 표준과는 약간 다른 접근 방식임을 알려드립니다.
18-31
: mutationFn과 onSuccess/onError 핸들링이 잘 구현되어 있습니다queryClient를 통해 성공 시 쿼리를 무효화하는 로직이 잘 구현되어 있습니다. 이렇게 하면 폴더 삭제 후 UI가 최신 상태로 자동 업데이트됩니다. 또한 에러 핸들링도 적절히 구현되어 있어서, 에러 발생 시 사용자에게 더 나은 피드백을 제공할 수 있습니다.
src/features/received/model/mutations/useEditFolder.ts (1)
21-34
: 쿼리 무효화 및 에러 처리가 잘 구현되어 있습니다성공 시 적절한 쿼리 무효화와 에러 발생 시 에러 로깅이 잘 구현되어 있습니다. 이는 사용자 경험을 향상시키는 데 중요한 패턴입니다. 필요한 경우 추가적으로 토스트 메시지나 알림을 통해 사용자에게 작업 결과를 피드백하는 것도 고려해 볼 수 있습니다.
src/features/received/model/mutations/useCreateFolder.ts (1)
8-18
: API 호출 및 에러 처리가 잘 구현되어 있습니다비동기 함수에 try-catch 블록을 사용하여 에러를 적절히 처리하고 있습니다. 또한 에러를 콘솔에 기록하고 상위로 다시 throw하여 호출자가 처리할 수 있게 한 것도 좋은 방식입니다.
src/features/received/model/mutations/useMoveCardToFolder.ts (1)
1-5
: 필요한 import 구문이 잘 구성되어 있습니다.import 구문이 깔끔하게 정리되어 있고 필요한 라이브러리와 내부 모듈만 가져오고 있습니다.
export const useMoveCardToFolder = () => { | ||
return useMutation({ | ||
mutationFn: (variables: { folderId: number; cardIds: number[] }) => | ||
_moveCardToFolder(variables.folderId, variables.cardIds), | ||
onSuccess: () => { | ||
console.log('받은 명함을 폴더에 저장 성공'); | ||
}, | ||
onError: (error) => { | ||
console.error('받은 명함을 폴더에 저장 실패 :', error); | ||
}, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
mutation 훅에 쿼리 무효화 및 중복 오류 처리 개선이 필요합니다.
현재 useMoveCardToFolder
훅에는 다음과 같은 개선 사항이 필요합니다:
- 내부 함수와
onError
콜백에서 오류 로깅이 중복됩니다. - 명함 이동 후 관련 쿼리를 무효화(invalidate)하여 데이터를 새로고침해야 합니다.
- 사용자에게 피드백을 제공하는 메커니즘이 없습니다(토스트 메시지 등).
- 반환 값에 대한 타입 정의가 명시적이면 더 좋을 것 같습니다.
+ import { queryClient } from '@/shared/apis/queryClient';
+ import { QUERY_KEYS } from '@/shared/constants/queryKeys';
+ import { toast } from '@/shared/components/toast';
export const useMoveCardToFolder = () => {
return useMutation({
mutationFn: (variables: { folderId: number; cardIds: number[] }) =>
_moveCardToFolder(variables.folderId, variables.cardIds),
onSuccess: () => {
- console.log('받은 명함을 폴더에 저장 성공');
+ // 관련 쿼리 무효화
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.RECEIVED_CARDS] });
+ queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.FOLDERS] });
+ // 사용자 피드백
+ toast.success('명함이 성공적으로 이동되었습니다.');
},
onError: (error) => {
- console.error('받은 명함을 폴더에 저장 실패 :', error);
+ // 사용자 피드백 (오류 로깅은 내부 함수에서 이미 처리)
+ toast.error('명함 이동 중 오류가 발생했습니다.');
},
});
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (15)
src/app/(received)/received/interesting/page.tsx (2)
18-20
: useEffect 의존성 배열 검토
현재isLoading
,serverReceivedCards
를 의존성 배열에 포함하여, 로딩 기간 중에만setReceivedCards
를 수행합니다. 로딩이 끝나도 재렌더가 필요하다면, 조건문을 좀 더 세분화하거나isLoading === false
시점에만 동작하도록 조정해 볼 수도 있습니다.
27-27
: 서버 데이터 매핑 로직
serverReceivedCards.map
는 정상적으로 데이터를 순회합니다. 다만, 카드에 식별자(id
등)가 있다면index
대신id
를key
에 사용하는 것을 권장합니다.src/features/received/ui/receivedCardView.tsx (7)
2-2
: 불필요한 상태 불러오기 확인
useEffect, useState
를 모두 import하셨는데, 실제로 사용 중인지 다시 확인해 주세요. 사용하지 않는 훅이라면 제거해 간결성을 유지할 수 있습니다.
12-15
: 폴더 관리자용 Mutation 훅 적절성
useCreateFolder
,useDeleteFolder
,useEditFolder
훅을 모아서 관리하는 구조가 유지보수에 용이합니다. 다만 폴더를 서버에서 가져오는 로직(useFoldersQuery
)과의 관계도 명확히 구분되어야 합니다.
21-24
: props 타입 명시
ReceivedCardViewProps
는 명확히 정의되어 있습니다. 폴더 ID가null
일 경우 전체 보기, 숫자일 경우 해당 폴더만 보여주는 등, 타입에 따른 동작스펙을 주석으로 남겨주면 가독성이 높아집니다.
30-32
: 폴더 이름 상태 분리
folderName
,newFolderName
,updatedFolderName
등 세 가지 상태가 혼재되어 있습니다. 의미 구분이 확실하면 상관없지만, 향후 유지보수 시 혼동할 여지가 있으므로 주석이나 네이밍 등으로 구분도를 높이는 걸 권장합니다.
37-39
: 폴더 서버 Mutation 훅 사용
serverCreateFolder
,serverEditFolder
,serverDeleteFolder
등의 네이밍이 직관적입니다. 성공/실패 콜백이 필요한 경우, 해당 훅에onSuccess
/onError
옵션을 추가해 재사용성을 높일 수 있습니다.
95-99
: 폴더 아이콘 클릭 시점에 대한 기능 확인
headerRightHandler();
,console.log(folders);
로직은 디버깅용으로 보입니다. 프로덕션에서 필요 없다면 제거 권장합니다.
118-160
: 모달 내부 구현 적절성
폴더 업데이트, 추가, 설정까지 모두 하나의 모달로 통합하였고,isUpdate
/isAdd
/ 이도 아닐 때의 분기 처리가 간결합니다. 라벨, 인풋, 버튼 등의 UI가 의도대로 동작하는지 UX 테스트를 통해 한 번 더 확인하면 좋겠습니다.src/app/(received)/received/page.tsx (6)
3-4
: Next.js 라우팅 관련 Import
useRouter
와React, useEffect, useState
가 함께 사용되고 있습니다. 불필요한 훅이 없는지 확인해 보세요.
6-9
: 폴더 및 받은 명함 쿼리 훅
useFoldersQuery
,useReceivedCardsQuery
,useFolderStore
,useReceivedCardsStore
가 한 파일에서 모두 선언되어 있어 데이터 흐름이 다소 복잡해 보일 수 있습니다. 유지보수를 위해 구조를 단순화하거나, 한눈에 알아볼 수 있는 주석을 달아두는 것도 방법입니다.
23-23
: 모달 State 관리
isChooseModalOpen, openChooseModal, closeChooseModal
는 네이밍과 역할이 명확해 보입니다. 다른 모달과 혼동되지 않도록 주석 등으로 구분해 두면 좋겠습니다.
30-30
: 추가 의존성 포함 여부
혹시serverFolders
외 다른 값도 이useEffect
에서 사용된다면, 모두 의존성 배열에 포함해야 하니 주의 부탁드립니다.
32-33
: 명함 데이터 세팅 useEffect
isCardsLoading === false
시점에만serverReceivedCards
를setReceivedCards
하는 로직 역시 자연스럽습니다. 다만, 로딩 외에 에러 처리 등도 고려해 보시면 좋겠습니다.
41-41
: Appbar에서 onRightClickSecond
Menu를 열기 위한 함수처럼 보입니다. 코드 유지보수를 위해 다른 props와 네이밍 일관성을 맞추는 것이 좋겠습니다. (onRightClick
,onMenuClick
등)
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/app/(received)/received/interesting/page.tsx
(2 hunks)src/app/(received)/received/page.tsx
(1 hunks)src/features/received/ui/choose/chooseReceivedCardView.tsx
(1 hunks)src/features/received/ui/receivedCardList.tsx
(1 hunks)src/features/received/ui/receivedCardView.tsx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/features/received/ui/receivedCardList.tsx
- src/features/received/ui/choose/chooseReceivedCardView.tsx
🧰 Additional context used
🧠 Learnings (1)
src/app/(received)/received/interesting/page.tsx (2)
Learnt from: semnil5202
PR: depromeet/Took-FE#149
File: src/app/(received)/received/interesting/page.tsx:18-20
Timestamp: 2025-03-31T22:59:17.224Z
Learning: React useEffect 훅에서는 내부에서 사용하는 모든 변수(특히 props나 state)를 의존성 배열에 포함해야 합니다. 단, Zustand와 같은 상태 관리 라이브러리에서 제공하는 setter 함수는 안정적이므로 의존성 배열에 포함하지 않아도 됩니다.
Learnt from: semnil5202
PR: depromeet/Took-FE#149
File: src/app/(received)/received/choose/page.tsx:18-20
Timestamp: 2025-03-31T22:59:18.554Z
Learning: React useEffect Hook을 사용할 때는 내부에서 사용하는 모든 값(변수, 함수 등)을 의존성 배열에 포함시켜야 합니다. 그렇지 않으면 해당 값이 변경되었을 때 effect가 실행되지 않아 예상치 못한 버그가 발생할 수 있습니다.
🧬 Code Definitions (2)
src/app/(received)/received/interesting/page.tsx (1)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(27-35)
src/features/received/ui/receivedCardView.tsx (3)
src/features/received/model/store/useFoldersStore.ts (1)
useFolderStore
(13-37)src/features/received/ui/foldersList.tsx (1)
FoldersList
(14-42)src/features/received/ui/receivedCardList.tsx (1)
ReceivedCardList
(12-24)
🔇 Additional comments (24)
src/app/(received)/received/interesting/page.tsx (4)
3-3
: 불필요한 주석 제거 제안
import React, { useEffect } from 'react';
구문은 정상적이지만, 만약 더 이상 필요치 않은 주석이나 추가 설명이 있다면 제거를 고려해 주세요. 간결한 코드 유지에 도움이 됩니다.
5-6
: 쿼리 훅과 스토어 훅 분리의도 확인
useReceivedCardsQuery
와useReceivedCardsStore
를 함께 사용해 명함 데이터를 가져오고, 상태를 저장하는 것으로 보입니다. 현재 구조는 문제 없으나, 동일 데이터 흐름이 중복되지 않도록 주의해 주세요.
15-16
: 서버 데이터와 스토어 데이터의 일관성
서버에서 받은 카드 리스트(serverReceivedCards
)와isLoading
을 함께 받아 사용하는 로직이 적절합니다. 단, 뒤이어useEffect
에서 조건을 검사하므로,isLoading
이false
때 자동으로 스토어에 데이터를 추가하는 흐름도 고려해볼 수 있습니다.
25-25
: Appbar 컴포넌트에 대한 prop 확인
page="mypage"
/title="흥미로운 명함"
/onLeftClick={handleBack}
조합이 정상 동작하는지 확인 바랍니다. 특히onLeftClick
콜백이 올바른 라우팅 혹은 화면 전환을 처리할 수 있는지 체크해 주세요.src/features/received/ui/receivedCardView.tsx (14)
17-17
: 폴더 리스트 UI 컴포넌트 네이밍
FoldersList
는 직관적인 이름입니다. 다만, 사용처가 늘어날 경우 재사용성이 확보되도록 최대한 범용적으로 작성하는 것도 좋습니다.
19-19
: ReceivedCardList 컴포넌트 의존성
ReceivedCardList
가selectedFolderId
만 받아서 렌더링하는 구조가 명확하고 단순합니다. props 검증(PropTypes or TS interface)도 적절히 되어 있는지 점검해 주세요.
26-29
: 상태 관리 네이밍
isUpdate
,isAdd
등 boolean 변수가 직관적이어서 좋습니다. 향후 상태가 복잡해지면enum
이나 별도 상태 정의가 필요할 수 있습니다.
35-35
: useFolderStore 디스트럭처링
folders, addFolder, updateFolder, deleteFolder
를 한번에 가져오고 있습니다. UI 상에서 잘 구분되어 사용되는지 확인 부탁드립니다.
41-43
: 폴더 선택 핸들러
handleFolderSelect
에서setSelectedFolderId
만 호출하도록 구성하셨습니다. 이 로직이 외부에서 정상적으로 useQuery 등과 연동되는지 다시 한번 확인해 주세요.
45-47
: 폴더 추가 핸들러
isAdd
를true
로 설정하는 간단한 로직이지만, Modal이나 렌더링 분기 처리가 제대로 동작하는지 한 번 더 살펴보면 좋겠습니다.
48-51
: 폴더 수정 핸들러
폴더 이름을 받아folderName
을 설정하고isUpdate = true
로 전환하는 구조가 깔끔합니다. 수정 로직이 분리되어 있어 유지보수에 용이합니다.
52-57
: 폴더 삭제 로직 및 UI 연동
서버 삭제(serverDeleteFolder
) 후 로컬 스토어(deleteFolder
)까지 업데이트하여 상태를 동기화하는 흐름이 올바릅니다. 성공 후toast.success
가 뜨는 시점도 적절합니다.
59-71
: 폴더 수정 시 Enter 키 이벤트 처리
handleUpdateKeyDown
내에서Enter
이벤트 시 로컬과 서버 모두 업데이트한 뒤, 모달을 닫고 Toast 메시지를 띄웁니다. 사용자가 폴더 이름을 수정한 뒤 바로 Enter를 누르기만 하면 되므로 UX가 좋을 것으로 보입니다.
72-82
: 폴더 추가 시 Enter 키 이벤트 처리
수정 로직과 유사하게handleAddKeyDown
은 신규 폴더 추가 과정을 잘 처리합니다. Enter 키 하나로 손쉽게 반영되는 점이 장점입니다.
84-89
: 모달이 닫힐 때 상태 초기화
isModalOpen
이 닫히면isUpdate
,isAdd
상태를 모두false
로 초기화하는 방식이 간단하고 확실합니다. 모달 밖 다른 로직에 영향이 없는지 한 번만 더 확인해 주세요.
104-104
: FoldersList 컴포넌트 렌더링
<FoldersList handleFolderSelect={handleFolderSelect} />
를 통해 폴더 리스트를 보여주고 선택 시 상위 컴포넌트의 상태를 업데이트하는 구조가 명확합니다.
105-105
: 스크롤바 숨김 클래스 재확인
scrollbar-hide
클래스가 잘 동작하는지, 모바일 환경에서 정상 동작하는지 한 번 더 점검해 보세요.
115-115
: ReceivedCardList 연동
<ReceivedCardList selectedFolderId={selectedFolderId} />
를 통해 폴더 선택 상태가 명함 목록에 정상 전달됩니다. 프롭 타입 정의를 통해selectedFolderId
가 null일 때 전체 목록을 보여주는지 재확인 바랍니다.src/app/(received)/received/page.tsx (6)
19-20
: 폴더 선택 State
selectedFolderId
에 대해null
일 때 전체 폴더, 숫자일 때 특정 폴더 식으로 로직이 짜여 있으면 좋습니다. 이미 적용되어 있다면 문제 없습니다.
21-21
: useFoldersQuery와 로딩 상태
isLoading: isFoldersLoading
라는 별칭 사용이 가독성에 좋습니다.
25-26
: 스토어 연결
setFolders
,setReceivedCards
를 통한 로컬 스토어 동기화가 올바르게 동작하는지, 해당 스토어가 어디에서 사용되고 있는지 점검 부탁드립니다.
28-29
: 폴더 데이터 세팅 useEffect
isFoldersLoading === false
시점에serverFolders
를setFolders
하는 로직이 적절합니다. 다만, 폴더 데이터가 변경될 때마다 갱신이 이뤄질 수 있도록 의존성 배열 역시 적합하게 구성돼 있습니다.
34-34
: 스토어 업데이트 의존성
setReceivedCards
가 제대로 동작하는지, 다른 컴포넌트에서 해당 스토어 데이터가 업데이트되는 타이밍에 맞춰 갱신되는지 검증해 주세요.
43-43
: ReceivedCardView 연결
<ReceivedCardView selectedFolderId={selectedFolderId} setSelectedFolderId={setSelectedFolderId} />
를 통해 선택 폴더 상태를 전달하는 방식이 명확합니다. 문제 없어 보입니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/features/received/ui/choose/chooseReceivedCardView.tsx (2)
36-44
: 🛠️ Refactor suggestion삭제 및 폴더 이동 함수 개선 제안
현재 handleDelete 함수는 서버 응답을 기다리지 않고 즉시 UI를 업데이트하고 있습니다. 이는 서버 요청이 실패할 경우 UI와 서버 상태가 불일치하게 될 위험이 있습니다. useDeleteReceivedCards와 useMoveCardToFolder의 onSuccess 콜백을 활용하는 것이 더 안전합니다.
const handleDelete = () => { - deleteCards(selectedCardIds); - deleteServerCards({ cardIds: selectedCardIds }); + deleteServerCards({ + cardIds: selectedCardIds, + onSuccess: () => { + deleteCards(selectedCardIds); + setSelectedCardIds([]); + } + }); }; const handleMoveToFolder = (folderId: number) => { - moveToFolder({ folderId: folderId, cardIds: selectedCardIds }); + moveToFolder({ + folderId: folderId, + cardIds: selectedCardIds, + onSuccess: () => { + setSelectedCardIds([]); + closeSettingModal(); + } + }); - closeSettingModal(); };
46-50
: 🛠️ Refactor suggestionuseEffect의 의존성 배열 및 조건부 로직 개선
현재 useEffect는 isFetching이 변경될 때마다 실행되고, isFetching이 false일 때 카드를 설정합니다. 하지만 이 접근 방식은 다음과 같은 문제가 있을 수 있습니다:
- isFetching은 요청 시작과 완료 시점 모두에서 false에서 true로, true에서 false로 변경될 수 있어 불필요한 렌더링이 발생할 수 있습니다.
- 데이터가 undefined일 때도 setReceivedCards가 호출될 수 있습니다.
useEffect(() => { - if (!isFetching) { + if (!isFetching && cards.length > 0) { setReceivedCards(cards); } -}, [isFetching]); +}, [isFetching, cards, setReceivedCards]);
🧹 Nitpick comments (9)
src/features/received/model/mutations/useDeleteReceivedCard.ts (1)
19-34
: useDeleteReceivedCards 훅의 구현 관련 피드백훅의 구현은 전반적으로 잘 되어 있으나, 다음 사항을 개선하면 좋겠습니다:
- 26-27줄의 console.log는 배포 전에 제거하는 것이 좋습니다.
- 특정 폴더의 카드만 삭제했을 때도 모든 폴더의 쿼리를 무효화하게 될 수 있습니다. 이는 불필요한 API 호출을 발생시킬 수 있습니다.
return useMutation({ mutationFn: (variables: { cardIds: number[] }) => _deleteReceivedCards(variables.cardIds), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [CARD_QUERY_KEY, folderId] }); - console.log('받은 명함 삭제 성공'); toast.success('명함을 삭제했어요'); }, onError: (error) => { console.error('받은 명함 삭제 실패:', error); toast.error('삭제에 실패했어요. 다시 시도해주세요.'); }, });
src/features/received/model/mutations/useDeleteFolder.ts (1)
19-34
: useDeleteFolder 훅의 구현 관련 피드백폴더 삭제 훅의 구현은 전반적으로 잘 되어 있으나, 다음 사항을 개선하면 좋겠습니다:
- 25-26줄의 console.log는 배포 전에 제거하는 것이 좋습니다.
- 쿼리 무효화 로직이 onSuccess 부분에서 toast 메시지보다 아래에 위치하고 있습니다. 일관성을 위해 다른 훅과 동일한 순서로 배치하는 것이 좋습니다.
return useMutation({ mutationFn: (variables: { folderId: number }) => _deleteFolder(variables.folderId), onSuccess: () => { - console.log('폴더 삭제 성공'); + queryClient.invalidateQueries({ queryKey: [FOLDER_QUERY_KEY] }); toast.success('폴더가 삭제되었어요.'); - queryClient.invalidateQueries({ queryKey: [FOLDER_QUERY_KEY] }); }, onError: (error) => { console.error('폴더 삭제 실패:', error); toast.error('폴더 삭제에 실패했어요. 다시 시도해주세요.'); }, });src/features/received/ui/choose/chooseReceivedCardView.tsx (4)
30-34
: toggleChecked 함수 내 console.log 제거 필요toggleChecked 함수 내에 디버깅용 console.log가 남아있습니다. 배포 전에 제거해주세요.
const toggleChecked = (id: number) => { setSelectedCardIds((prev) => (prev.includes(id) ? prev.filter((cardId) => cardId !== id) : [...prev, id])); - console.log(selectedCardIds); };
52-52
: 로딩 상태 UI 개선 필요현재 로딩 상태 표시는 임시 구현이라고 주석에 명시되어 있습니다. 실제 프로덕션에서는 Skeleton UI나 스피너 같은 보다 사용자 친화적인 로딩 인디케이터를 구현하는 것이 좋습니다.
프로젝트에 사용 중인 디자인 시스템에 맞는 로딩 컴포넌트를 활용하는 것을 권장합니다.
81-94
: 카드 목록 렌더링 최적화 제안카드 목록을 렌더링할 때 key로 index를 사용하고 있습니다. 목록의 순서가 변경되거나 항목이 추가/제거될 때 React의 재조정 알고리즘에 문제가 발생할 수 있습니다. 또한 동일한 key를 ReceivedCard 컴포넌트에도 사용하고 있습니다.
{receivedCards.map((card, index) => { return ( - <div key={index} className="flex max-w-full items-center gap-4"> + <div key={card.id} className="flex max-w-full items-center gap-4"> <ReceivedCheckbox checked={selectedCardIds.includes(card.id)} onCheckedChange={() => toggleChecked(card.id)} /> <div className="min-w-0 flex-1"> - <ReceivedCard key={index} cardData={card} /> + <ReceivedCard cardData={card} /> </div> </div> ); })}
95-105
: 바텀 모달 및 폴더 목록 관련 개선점폴더 목록을 표시하는 바텀 모달에서 다음과 같은 개선 사항을 고려해볼 수 있습니다:
- 폴더 항목에 폴더 아이콘 추가하여 시각적 구분 강화
- 폴더 내 카드 수 표시하여 사용자에게 추가 정보 제공
- 폴더가 없을 때 안내 메시지 표시
폴더 목록이 비어 있을 때 안내 메시지를 추가하는 예시:
<BottomModal isModalOpen={isSettingModalOpen} closeModal={closeSettingModal}> <BottomModalTitle>폴더 설정</BottomModalTitle> + {folders.length === 0 && ( + <div className="flex justify-center items-center p-4 text-gray-400"> + 생성된 폴더가 없습니다. 폴더를 추가해보세요. + </div> + )} {folders.map((folder, index) => { return ( <BottomMenuItem key={index} onClick={() => handleMoveToFolder(folder.id)}> {folder.name} </BottomMenuItem> ); })} </BottomModal>src/app/(received)/received/page.tsx (1)
28-34
: useEffect 사용 최적화 필요서버 데이터를 로컬 스토어에 동기화하는 useEffect 훅들이 구현되어 있습니다. 이전 리뷰 코멘트에서도 언급되었듯이, useEffect 로직을 최적화할 수 있습니다.
- 두 개의 useEffect를 하나로 합칠 수 있습니다.
- 조건부 로직에 데이터 존재 여부도 확인하는 것이 좋습니다.
- useEffect(() => { - if (!isFoldersLoading) setFolders(serverFolders); - }, [isFoldersLoading, serverFolders, setFolders]); - - useEffect(() => { - if (!isCardsLoading) setReceivedCards(serverReceivedCards); - }, [isCardsLoading, serverReceivedCards, setReceivedCards]); + useEffect(() => { + if (!isFoldersLoading && serverFolders.length > 0) { + setFolders(serverFolders); + } + + if (!isCardsLoading && serverReceivedCards.length > 0) { + setReceivedCards(serverReceivedCards); + } + }, [ + isFoldersLoading, isCardsLoading, + serverFolders, serverReceivedCards, + setFolders, setReceivedCards + ]);src/features/received/ui/receivedCardView.tsx (2)
79-84
: 모달이 닫힐 때newFolderName
과updatedFolderName
을 초기화해 주세요.
isModalOpen
이false
가 될 때setIsUpdate
와setIsAdd
를 리셋하고 있지만, 폴더 이름 관련 다른 상태(newFolderName
,updatedFolderName
)는 그대로 유지되고 있습니다. 모달을 재오픈할 때 이전 입력값이 남아 사용자 혼동이 발생할 수 있으니 함께 리셋하는 방안을 고려하십시오.
112-123
: ‘10/10’ 안내 문구와 실제 길이 제한 검증이 일치해야 합니다.
현재 ‘10/10’라는 안내 문구는 있으나, 폴더명을 10자 초과로 입력했을 때 별도의 처리 없이 서버에 그대로 요청이 전달될 가능성이 있습니다. 사용자의 의도치 않은 입력을 방지하고자, 입력 값의 최대 글자 수를 실제로 제한하거나 초과 시 에러(또는 안내 메시지)를 표시하는 로직을 구현해 주세요.Also applies to: 124-133
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
src/app/(received)/received/interesting/page.tsx
(2 hunks)src/app/(received)/received/page.tsx
(1 hunks)src/features/received/model/mutations/useCreateFolder.ts
(1 hunks)src/features/received/model/mutations/useDeleteFolder.ts
(1 hunks)src/features/received/model/mutations/useDeleteReceivedCard.ts
(1 hunks)src/features/received/model/mutations/useEditFolder.ts
(1 hunks)src/features/received/model/mutations/useMoveCardToFolder.ts
(1 hunks)src/features/received/model/queries/useReceivedCardsQuery.ts
(1 hunks)src/features/received/ui/choose/chooseReceivedCardView.tsx
(1 hunks)src/features/received/ui/foldersList.tsx
(1 hunks)src/features/received/ui/receivedCardView.tsx
(2 hunks)src/shared/ui/Toast/sonner.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- src/features/received/model/mutations/useEditFolder.ts
- src/features/received/model/mutations/useCreateFolder.ts
- src/shared/ui/Toast/sonner.tsx
- src/features/received/ui/foldersList.tsx
- src/features/received/model/queries/useReceivedCardsQuery.ts
- src/app/(received)/received/interesting/page.tsx
- src/features/received/model/mutations/useMoveCardToFolder.ts
🧰 Additional context used
🧬 Code Definitions (5)
src/features/received/model/mutations/useDeleteReceivedCard.ts (1)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
CARD_QUERY_KEY
(7-7)
src/features/received/model/mutations/useDeleteFolder.ts (1)
src/features/received/model/queries/useFoldersQuery.ts (1)
FOLDER_QUERY_KEY
(7-7)
src/features/received/ui/choose/chooseReceivedCardView.tsx (4)
src/features/received/model/store/useReceivedCardsStore.ts (1)
useReceivedCardsStore
(11-18)src/features/received/model/mutations/useDeleteReceivedCard.ts (1)
useDeleteReceivedCards
(19-34)src/features/received/model/mutations/useMoveCardToFolder.ts (1)
useMoveCardToFolder
(20-33)src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(27-34)
src/features/received/ui/receivedCardView.tsx (3)
src/features/received/model/store/useFoldersStore.ts (1)
useFolderStore
(13-37)src/features/received/ui/foldersList.tsx (1)
FoldersList
(14-47)src/features/received/ui/receivedCardList.tsx (1)
ReceivedCardList
(12-24)
src/app/(received)/received/page.tsx (5)
src/features/received/model/queries/useReceivedCardsQuery.ts (1)
useReceivedCardsQuery
(27-34)src/features/received/model/queries/useFoldersQuery.ts (1)
useFoldersQuery
(20-27)src/features/received/model/useModal.ts (1)
useModal
(3-28)src/features/received/model/store/useFoldersStore.ts (1)
useFolderStore
(13-37)src/features/received/ui/receivedCardView.tsx (1)
ReceivedCardView
(25-158)
🔇 Additional comments (11)
src/features/received/model/mutations/useDeleteReceivedCard.ts (2)
1-7
: API 통신을 위한 적절한 모듈을 임포트하였습니다.React Query를 활용한 mutation과 toast 알림을 위한 필요 모듈들을 잘 가져왔습니다.
9-17
: 받은 명함 삭제 로직이 적절하게 구현되었습니다.명함 ID 배열을 받아 API를 호출하는 private 함수가 잘 구현되어 있습니다. 에러 처리와
로깅도 적절합니다.src/features/received/model/mutations/useDeleteFolder.ts (2)
1-7
: API 통신을 위한 적절한 모듈을 임포트하였습니다.React Query를 활용한 mutation과 toast 알림을 위한 필요 모듈들을 잘 가져왔습니다.
9-17
: 폴더 삭제 로직이 적절하게 구현되었습니다.폴더 ID를 받아 API를 호출하는 private 함수가 잘 구현되어 있습니다. 에러 처리와
로깅도 적절합니다.src/features/received/ui/choose/chooseReceivedCardView.tsx (2)
1-17
: 적절한 모듈 임포트 및 컴포넌트 구성필요한 모듈과 컴포넌트들을 잘 임포트하였습니다. 명함 관리에 필요한 mutation과 query 훅도 잘 구성되어 있습니다.
19-29
: 컴포넌트 상태 관리가 잘 구현되어 있습니다.필요한 상태와 스토어 훅을 적절히 사용하여 컴포넌트를 구성했습니다.
isAnyChecked 변수를 통해 선택된 카드가 있는지 확인하는 로직도 좋습니다.src/app/(received)/received/page.tsx (4)
3-9
: 적절한 모듈 임포트 및 구조화필요한 모듈들을 잘 임포트하였고, 특히 폴더와 카드 관련 쿼리 및 스토어 훅들이 적절히 구성되어 있습니다.
19-21
: 상태 관리 및 쿼리 훅 사용이 적절합니다선택된 폴더 ID를 상태로 관리하고 이를 쿼리 훅에 전달하는 방식이 잘 구현되어 있습니다. React Query의 장점을 잘 활용한 구조입니다.
41-44
: 컴포넌트 속성 전달이 잘 구현되어 있습니다Appbar와 ReceivedCardView 컴포넌트에 필요한 속성들이 잘 전달되고 있습니다. 특히 selectedFolderId를 null과 함께 안전하게 처리한 점이 좋습니다.
45-54
: 모달 및 라우팅 처리가 적절합니다명함 선택 모달과 라우팅 처리가 잘 구현되어 있습니다. 모달을 닫고 라우팅을 처리하는 순서도 적절합니다.
src/features/received/ui/receivedCardView.tsx (1)
134-154
: 폴더 리스트 처리 로직이 직관적으로 잘 구성되어 있습니다.
각 폴더별로 수정·삭제 UI를 직관적으로 구성했고, ‘추가하기’ 버튼으로 새로운 폴더를 등록할 수 있어 기능적으로 문제가 없어 보입니다.
const handleUpdateKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
const index = folders.findIndex((folder) => folder.name === folderName); | ||
|
||
if (e.nativeEvent.isComposing) return; | ||
if (e.key == 'Enter') { | ||
e.preventDefault(); | ||
const folderId = folders[index].id; | ||
updateFolder(folderId, updatedFolderName); | ||
serverEditFolder({ folderId, name: updatedFolderName }); | ||
closeModal(); | ||
} | ||
}; | ||
const handleAddKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (e.nativeEvent.isComposing) return; | ||
if (e.key == 'Enter') { | ||
e.preventDefault(); | ||
serverCreateFolder(newFolderName); | ||
addFolder(newFolderName); | ||
closeModal(); | ||
} | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
폴더 수정/추가 시 에러 처리와 입력 검증이 필요합니다.
handleUpdateKeyDown
과 handleAddKeyDown
모두 성공/실패 여부와 상관없이 로컬 스토어를 먼저 변경하고 모달을 닫고 있습니다. 서버 호출이 실패할 경우를 대비해, 에러 처리를 고려해야 합니다. 또한 폴더명이 빈 문자열이거나 특정 길이를 초과할 때를 대비한 유효성 검사 로직을 추가하면 사용자의 실수를 방지할 수 있습니다.
const handleDelete = (id: number) => { | ||
serverDeleteFolder({ folderId: id }); | ||
deleteFolder(id); | ||
closeModal(); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
서버 호출 결과에 대한 에러 처리가 필요해 보입니다.
로컬 스토어(deleteFolder
)가 서버 응답 결과와 무관하게 먼저 실행되어, 서버 삭제가 실패했을 경우 폴더 정보가 일관성 없이 삭제될 위험이 있습니다. 다음과 같이 React Query의 onSuccess
, onError
콜백을 통해 정확히 처리하는 방안을 고려해 주세요.
- const handleDelete = (id: number) => {
- serverDeleteFolder({ folderId: id });
- deleteFolder(id);
- closeModal();
- };
+ const handleDelete = (id: number) => {
+ serverDeleteFolder(
+ { folderId: id },
+ {
+ onSuccess: () => {
+ deleteFolder(id);
+ closeModal();
+ },
+ onError: () => {
+ // 에러 처리 로직 추가 예시
+ console.error('서버 폴더 삭제 중 오류가 발생했습니다');
+ },
+ }
+ );
+ };
📌 개요
#134 [Feat] 받은 명함 페이지 - API 연결 로직, 바텀 시트 추가 구현
📋 변경사항
받은 명함 페이지에서의 API를 연결하고, 바텀 시트를 추가로 구현했습니다
기능
화면
✅ 체크사항
기타
Summary by CodeRabbit