Skip to content

Refactor: API 에러 처리 공통 핸들러 도입 및 onError 정책 통일 - #202

Merged
hyun907 merged 3 commits into
developfrom
refactor/#112/api-error-handle
Apr 28, 2026
Merged

Refactor: API 에러 처리 공통 핸들러 도입 및 onError 정책 통일#202
hyun907 merged 3 commits into
developfrom
refactor/#112/api-error-handle

Conversation

@hyun907

@hyun907 hyun907 commented Apr 26, 2026

Copy link
Copy Markdown
Member

📝 PR 유형

  • 🔨 리팩토링

🔔 관련된 이슈 넘버

close #112

✅ 작업 목록

1. API 에러 처리 공통화

  • handleApiError 유틸 함수 추가 (shared/api/handleApiError.ts)
  • 분산되어 있던 토스트 / 에러 처리 로직 통합
handleApiError(error, {
  toast,
  fallback: '저장에 실패했어요. 다시 시도해 주세요.',
  context: 'expense.create',
  // preferServerMessage: true
});

에러 처리 정책

정책 동작
401 / 403 silent (토스트 없음, client.ts에서 처리)
일반 에러 fallback 문구로 토스트 노출
preferServerMessage: true HTTPError 시 서버 message 우선
로깅 console.error('[API Error] {context}:', error)

2. client.ts beforeError 개선

  • raw text → JSON 파싱 후 body.message만 추출
  • 파싱 실패 시 기존 ky 메시지 유지

3. React Query 에러 처리 구조 정리

  • useMutation 선언부에 onError 고정
  • 호출부 onError 제거 (ExpenseRecordFunnel)

4. 기존 코드 마이그레이션

  • ExpenseEditBottomSheet (update/delete)
  • ExpenseRecordFunnel (submit)
  • useWithdraw (preferServerMessage: true)
  • useInquiryForm

5. 누락된 onError 보강

  • useSpendingReview (saveRemind)
  • AddCategory (create/update)
  • AddCategoryStep (createCategory)
  • Notifications.readAll (silent 처리 + console.error만)

6. 토스트 문구 톤 통일

  • "다시 시도해주세요" → "다시 시도해 주세요"

🍰 논의사항

현재 API mutation 에러 처리는 handleApiError 공통 핸들러로 정리했지만, 이후 새로운 mutation을 추가할 때 onError를 누락하면 동일한 문제가 다시 발생할 수 있을 것 같습니다.

이번 PR에서도 기존 mutation 중 onError가 누락된 케이스가 발견되어 보강했기 때문에, 장기적으로는 useMutation 사용 시 onError 누락을 ESLint 룰로 감지하도록 추가할지 논의해보고 싶습니다.

필수 적용까지는 아니더라도, 최소한 경고 수준으로 먼저 도입하면 API 실패 시 사용자 피드백이 빠지는 문제를 예방할 수 있을 것 같습니다.


📷 ETC

사이드 이펙트 점검

변경 영향 결과
error.message 포맷 변경 기존 raw message 의존 코드 useWithdraw만 영향 → 이미 수정 완료
JSON 파싱 실패 응답이 JSON이 아닐 경우 try/catch로 fallback
HTTPError 외 에러 네트워크/타임아웃 fallback 사용 (의도된 동작)
401/403 silent 처리 사용자 토스트 없음 console.error로 추적 가능

Summary by CodeRabbit

릴리스 노트

  • 버그 수정
    • 다양한 기능(출금, 비용, 카테고리, 문의, 리뷰, 알림)의 오류 메시지 처리를 일관되게 개선했습니다.
    • API 오류에 대한 통일된 처리 방식을 도입하여 사용자 피드백이 보다 명확해졌습니다.

@hyun907
hyun907 requested a review from ssilver01 April 26, 2026 14:01
@hyun907 hyun907 self-assigned this Apr 26, 2026
@hyun907 hyun907 added the 🔨 Refactor 코드 리팩토링 label Apr 26, 2026
@hyun907 hyun907 linked an issue Apr 26, 2026 that may be closed by this pull request
5 tasks
@vercel

vercel Bot commented Apr 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nitrogen-front-web Ready Ready Preview, Comment Apr 26, 2026 2:01pm

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

Walkthrough

API 에러 핸들링을 통일하기 위해 공유 handleApiError 유틸리티를 도입하고, 여러 컴포넌트와 훅의 산발적인 에러 처리를 이 핸들러로 교체합니다. API 클라이언트의 에러 파싱 로직도 개선되어 서버 JSON 응답을 우선 처리합니다.

Changes

Cohort / File(s) Summary
공유 API 에러 핸들러 신규 추가
apps/web/src/shared/api/handleApiError.ts, apps/web/src/shared/api/index.ts
새로운 handleApiError 유틸리티와 관련 타입(ToastApi, HandleApiErrorOptions) 추가. 컨텍스트별 일관된 에러 로깅과 토스트 표시를 담당합니다.
API 클라이언트 에러 처리 개선
apps/web/src/shared/api/client.ts
beforeError 훅에서 응답 바디를 JSON으로 파싱하여 message 필드를 추출하도록 변경. 텍스트 기반 처리에서 구조화된 에러 페이로드 처리로 전환합니다.
인증 및 조회 관련 뮤테이션 에러 처리 통일
apps/web/src/features/auth/model/useWithdraw.ts, apps/web/src/features/inquiry/model/useInquiryForm.ts, apps/web/src/features/review/model/useSpendingReview.ts
기존 로컬 에러 메시지 구성을 handleApiError로 교체. 토스트 인스턴스, 폴백 메시지, 컨텍스트 식별자를 전달합니다.
비용 관련 뮤테이션 에러 처리 통일
apps/web/src/features/expense/ui/ExpenseEditBottomSheet.tsx, apps/web/src/features/expense/ui/steps/AddCategoryStep/AddCategoryStep.tsx, apps/web/src/widgets/expenseRecordFunnel/ui/ExpenseRecordFunnel.tsx
뮤테이션의 onError 콜백을 handleApiError로 통합. 기존 console.error 직접 호출과 인라인 토스트 표시를 제거합니다.
카테고리 및 알림 뮤테이션 에러 처리
apps/web/src/widgets/addCategory/ui/AddCategory.tsx, apps/web/src/widgets/notifications/ui/Notifications.tsx
카테고리 생성/업데이트 및 알림 조회 뮤테이션에 onError 핸들러 추가. 일관된 에러 처리 흐름을 적용합니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 에러들 모여서 한길로 가니
토스트 메시지가 톤 높게 울려
디버깅은 쉬워지고, 정책 변경은 간편해
에러 핸들러의 축제! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 '공통 핸들러 도입 및 onError 정책 통일'이라는 핵심 변경사항을 명확하게 요약하고 있습니다.
Linked Issues check ✅ Passed 모든 링크된 이슈 #112의 목표를 충족합니다: 공통 에러 핸들러 추가, onError 정책 통일, 기존 코드 마이그레이션, 로깅 일원화가 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 API 에러 처리 통일이라는 정의된 범위 내에 있으며, 관련 없는 코드 수정은 없습니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/#112/api-error-handle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

🎨 Storybook 배포 완료

📚 Storybook: https://6960ec095e9394ddeaa0f9f3-lbqaapbpai.chromatic.com/
🔍 Chromatic 빌드: https://www.chromatic.com/build?appId=6960ec095e9394ddeaa0f9f3&number=201

UI 변경사항을 확인해주세요!

@github-actions

Copy link
Copy Markdown

🎉 구현한 기능 Preview: https://nitrogen-front-ptknz8o9v-ssilver01s-projects.vercel.app

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/shared/api/handleApiError.ts`:
- Around line 39-42: The current logic uses error.message when
preferServerMessage is true, which can expose ky's generic messages; update
handleApiError to use a distinct server-specific property (e.g.,
error.serverMessage) instead of error.message when preferServerMessage is
requested, and only show that value if it exists; also modify client.ts's
beforeError handler to parse the JSON body and set error.serverMessage =
body.message (or undefined) whenever a server-provided message exists so you can
reliably distinguish server messages from ky's default messages; finally change
the toast call to prefer error.serverMessage (if preferServerMessage) else
fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 981f7883-85ef-403a-bac9-6cd6140de972

📥 Commits

Reviewing files that changed from the base of the PR and between af80426 and b14c4fb.

📒 Files selected for processing (11)
  • apps/web/src/features/auth/model/useWithdraw.ts
  • apps/web/src/features/expense/ui/ExpenseEditBottomSheet.tsx
  • apps/web/src/features/expense/ui/steps/AddCategoryStep/AddCategoryStep.tsx
  • apps/web/src/features/inquiry/model/useInquiryForm.ts
  • apps/web/src/features/review/model/useSpendingReview.ts
  • apps/web/src/shared/api/client.ts
  • apps/web/src/shared/api/handleApiError.ts
  • apps/web/src/shared/api/index.ts
  • apps/web/src/widgets/addCategory/ui/AddCategory.tsx
  • apps/web/src/widgets/expenseRecordFunnel/ui/ExpenseRecordFunnel.tsx
  • apps/web/src/widgets/notifications/ui/Notifications.tsx

Comment on lines +39 to +42
const serverMessage =
preferServerMessage && error instanceof HTTPError && error.message ? error.message : null;

toast.attention(serverMessage ?? fallback);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

preferServerMessage 사용 시 ky의 기본 에러 메시지가 노출될 위험

client.tsbeforeError는 응답 body를 JSON 파싱해 body.message가 있을 때만 error.message를 덮어씁니다. 따라서 응답이 JSON이 아니거나 message 필드가 없는 경우 error.message는 ky의 기본 문자열(예: Request failed with status code 500)이 그대로 남습니다. 현재 로직은 error.message가 truthy이기만 하면 이를 토스트에 노출하므로, preferServerMessage: trueuseWithdraw 같은 호출에서 사용자에게 영문 ky 메시지가 그대로 보일 수 있습니다.

서버가 실제로 메시지를 내려줬을 때만 이를 우선하도록, 서버 메시지를 별도 필드에 저장해 구분하는 것을 권장합니다.

♻️ 제안: 서버 메시지를 별도 필드로 보관
// apps/web/src/shared/api/client.ts (beforeError)
 if (error.response) {
   try {
     const body = (await error.response.clone().json()) as { message?: string };
     if (body?.message) {
       error.message = body.message;
+      (error as HTTPError & { serverMessage?: string }).serverMessage = body.message;
     }
   } catch {
     // JSON 파싱 실패 시 원본 message 유지
   }
 }
// handleApiError.ts
   const serverMessage =
-    preferServerMessage && error instanceof HTTPError && error.message ? error.message : null;
+    preferServerMessage && error instanceof HTTPError
+      ? ((error as HTTPError & { serverMessage?: string }).serverMessage ?? null)
+      : null;
📝 Committable suggestion

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

Suggested change
const serverMessage =
preferServerMessage && error instanceof HTTPError && error.message ? error.message : null;
toast.attention(serverMessage ?? fallback);
const serverMessage =
preferServerMessage && error instanceof HTTPError
? ((error as HTTPError & { serverMessage?: string }).serverMessage ?? null)
: null;
toast.attention(serverMessage ?? fallback);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/shared/api/handleApiError.ts` around lines 39 - 42, The current
logic uses error.message when preferServerMessage is true, which can expose ky's
generic messages; update handleApiError to use a distinct server-specific
property (e.g., error.serverMessage) instead of error.message when
preferServerMessage is requested, and only show that value if it exists; also
modify client.ts's beforeError handler to parse the JSON body and set
error.serverMessage = body.message (or undefined) whenever a server-provided
message exists so you can reliably distinguish server messages from ky's default
messages; finally change the toast call to prefer error.serverMessage (if
preferServerMessage) else fallback.

@ssilver01 ssilver01 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

공통 핸들러 도입 감사합니다 ! ggoodd

@hyun907
hyun907 merged commit 7e21281 into develop Apr 28, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 Refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ Refactor ] API 에러 핸들링(토스트) 통일 작업

2 participants