Skip to content

refactor: 중복확인 로직 useMutation으로 변경#394

Merged
heesunee merged 15 commits intodevelopfrom
refactor/#393/duplicate-check
Nov 19, 2025
Merged

refactor: 중복확인 로직 useMutation으로 변경#394
heesunee merged 15 commits intodevelopfrom
refactor/#393/duplicate-check

Conversation

@heesunee
Copy link
Contributor

@heesunee heesunee commented Sep 19, 2025

#️⃣ Related Issue

Closes #393

☀️ New-insight

  • TanStack Query에서, 단순히 요청 메서드 (GET/POST/PUT 등)로 query/mutation을 구분하지 않는다.
  • 보통 useQuery는 페이지 진입 시점이나 특정 key에 의존해서 자동으로 실행되는 쿼리에 적합합니다. 보통 GET 요청에 쓰이지만, 핵심은 데이터를 캐싱하고 필요시 refetch를 한다는 점이에요. 즉, 중복확인 처럼 사용자가 버튼(action)을 눌러야 실행되는 즉발성 요청에는 맞지않아요.
  • useMutation은 보통 PUT/POST 요청에 실행되는 쿼리에요. 이유는 보통 뭔가 요청을 보낼때는 사용자의 액션이 필요하기 때문이에요. 따라서 POST/PUT/DELETE 뿐만 아니라, 버튼을 눌러야 하는 GET 요청에도 자연스럽게 쓸 수있어요. 오히려 실행 시점이 명시적이고, 결과를 캐싱하지 않아도 되기 때문에 mutation이 더 적합하다고 생각했어요.

💎 PR Point

닉네임 중복확인의 경우

사용자가 입력 값을 바꾸고 버튼을 눌러야만 요청이 발생 -> 자동호출 필요없음
매번 다른 값으로 확인하므로 캐싱도 큰 의미 없음, 따라서 useMutation으로 수정하는게 맞다고 판단했습니다.

  NICKNAME_CHECK: () =>
    mutationOptions<boolean, Error, { nickname: string }>({
      mutationFn: async ({ nickname }) => {
        try {
          await get<void>(END_POINT.GET_NICKNAME_CHECK(nickname));
          return true;
        } catch (e) {
          if (axios.isAxiosError(e) && e.response?.status === HTTP_STATUS.CONFLICT) {
            return false;
          }
          throw e;
        }
      },
      retry: (failureCount, error) => {
        if (axios.isAxiosError(error) && error.response?.status === HTTP_STATUS.CONFLICT) {
          return false;
        }
        return failureCount < NICKNAME_DUPLICATE_FAILURE_COUNT;
      },
    }),
  • 추가적으로, HTTP_STATUS.CONFLICT(중복)의 경우 에러로 던지지 않고, false로 반환시켜 네트워크 오류와 중복상태를 명확히 구분할 수 있도록 수정했어요.
  const { mutateAsync: checkNickname } = useMutation(userMutations.NICKNAME_CHECK());
  • useMutation에서 refetch 대신 mutateAsync를 사용하도록 개선했어요.
  • mutate는 즉시 실행 후, 성공/실패 콜백 (onSucess, onError)로 결과를 다루지만, mutateAsync는 Promise를 반환하기 때문에 await/try-catch로 처리할 수 있어요
  • 닉네임 중복 확인 같은 상황에서는 오류(네트워크, 서버문제)도 try-catch로 한 번에 처리가 가능하고, 성공시 true/false를 바로 반환받아 상태를 바로 갱신하기 쉬워요 (nicknameStatus)따라서 handleCheckNickname 안에서 const available = await checkNickname({ nickname: nicknameValue }); 처럼 결과를 직관적으로 받을 수 있어요.

Summary by CodeRabbit

  • 신규 기능
    • 없음
  • 버그 수정
    • 닉네임 중복 확인의 오류 처리와 재시도 정책을 개선해, 중복 여부가 잘못 표시되는 문제를 줄였습니다.
  • 리팩터링
    • 닉네임 중복 확인 흐름을 요청 기반으로 단순화해 회원가입과 프로필 편집에서 동일한 방식으로 동작하도록 통합했습니다.
    • 확인 결과에 따라 상태 업데이트 로직을 명확히 하여 가용/중복 판단의 일관성을 높였습니다.
  • 잡무
    • 검증 관련 상수 정리로 중복 확인 재시도 기준을 명시적으로 관리합니다.

heesunee and others added 15 commits September 5, 2025 13:11
Co-authored-by: CHAEEUN KIM <154000318+bongtta@users.noreply.github.com>
* fix: (QA/3) 홈으로 페이지 이동할 때 배너 이미지 로드 전에 텍스트만 뜨는 문제 해결  (#384)

* fix: 홈으로 페이지 이동할 때 배너 이미지 로드 전에 텍스트만 뜨는 문제 해결 (#384)

* fix: 배너 최소 높이 설정 (#384)

* fix: 회원가입 design, gaEvent 수정 및 매칭 현황 내 중복 데이터 오류 해결 (#383)

* fix: sign-up 페이지 배경색 짤리는 오류 수정 (#383)

* fix: match-card-view gaEvent 삭제 (#383)

* fix: gaEvent 위치 수정 (#383)

* fix: 캐시가 섞이면서 데이터 중복되는 에러 해결 (#383)

* fix: 주석 제거 (#383)

---------

Co-authored-by: heesunee <heesun729@uos.ac.kr>

---------

Co-authored-by: Yewon Kim <163109964+yeeeww@users.noreply.github.com>
Co-authored-by: heesunee <heesun729@uos.ac.kr>
Co-authored-by: heesunee <heesun729@uos.ac.kr>
@coderabbitai
Copy link

coderabbitai bot commented Sep 19, 2025

Walkthrough

닉네임 중복 확인 로직을 useQuery 기반 재조회에서 useMutation 기반 명시적 호출로 전환했습니다. 회원가입과 프로필 수정 화면의 핸들러가 변경되었고, user-queries의 NICKNAME_CHECK가 제거되어 user-mutations에 추가되었습니다. 재시도 정책과 409(CONFLICT) 처리, 관련 상수 추가가 포함됩니다.

Changes

Cohort / File(s) Summary
UI - 닉네임 중복확인 전환
src/pages/edit-profile/edit-profile.tsx, src/pages/sign-up/components/signup-step.tsx
useQuery 기반 refetch 제거, useMutation(userMutations.NICKNAME_CHECK)로 전환. 핸들러에서 mutateAsync 호출로 true/false에 따라 nicknameStatus 설정. 에러 시 상태 복구 로직 조정.
API Surface - Mutations 추가
src/shared/apis/user/user-mutations.ts
NICKNAME_CHECK 추가: GET 호출 성공 시 true, 409(CONFLICT) 시 false 반환, 기타 에러 재throw. 재시도: CONFLICT면 미재시도, 그 외 NICKNAME_DUPLICATE_FAILURE_COUNT 기준.
API Surface - Queries 정리
src/shared/apis/user/user-queries.ts
NICKNAME_CHECK 항목 삭제. 관련 axios/HTTP_STATUS 의존 제거. 다른 쿼리는 변경 없음.
검증 상수
src/pages/sign-up/constants/validation.ts
NICKNAME_DUPLICATE_FAILURE_COUNT = 2 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant UI as Signup/EditProfile UI
  participant MUT as useMutation(NICKNAME_CHECK)
  participant API as GET /nickname/check
  note over UI,MUT: 닉네임 중복확인 로직을 useQuery → useMutation으로 전환

  U->>UI: 닉네임 중복확인 버튼 클릭
  UI->>MUT: mutateAsync({ nickname })
  MUT->>API: GET /nickname/check?nickname=...
  alt 200 OK
    API-->>MUT: success
    MUT-->>UI: true (available)
    UI-->>U: 사용 가능 상태 표시
  else 409 CONFLICT
    API-->>MUT: conflict
    MUT-->>UI: false (duplicate)
    UI-->>U: 중복 상태 표시
  else Other Error
    API-->>MUT: error
    MUT-->>UI: throw
    UI-->>U: 에러 처리/상태 초기화
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

refactor, 희선

Suggested reviewers

  • Dubabbi
  • bongtta
  • yeeeww

Poem

토끼는 깡총, 닉네임 톡톡
쿼리는 쉬고, 뮤테이션 콕콕
409면 고개를 흔들고
200이면 귀가 쫑긋 반짝
재시도 두 번, 깔끔한 약속
오늘도 깔끔히, 체크 완료! 🐰✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed PR 제목 "refactor: 중복확인 로직 useMutation으로 변경"은 닉네임 중복확인 로직을 useQuery에서 useMutation으로 이전하는 핵심 변경을 간결하고 명확하게 요약하고 있습니다. 제공된 변경 요약과 수정된 파일들(src/pages/, src/shared/apis/user/)의 내용이 제목과 일치합니다.
Linked Issues Check ✅ Passed PR은 이슈 #393의 요구사항을 충족합니다: useQuery 기반 닉네임 중복확인 로직을 useMutation으로 교체했고 userMutations에 NICKNAME_CHECK를 추가하여 HTTP 409(중복)를 false로 처리하며 retry 정책(NICKNAME_DUPLICATE_FAILURE_COUNT)을 적용했고, 컴포넌트들은 mutateAsync로 결과를 처리하도록 변경되었습니다. raw_summary에 보고된 파일별 변경사항이 이 구현 목표들을 뒷받침합니다.
Out of Scope Changes Check ✅ Passed 변경 범위는 닉네임 중복확인 로직, 관련 상수 추가(NICKNAME_DUPLICATE_FAILURE_COUNT), 및 해당 컴포넌트들에 국한되어 있으며 raw_summary에 따르면 다른 영역에 대한 불필요한 변경이나 부수적 영향은 발견되지 않습니다. userQueries의 다른 항목(MATCH_CONDITION 등)은 유지된 것으로 보입니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/#393/duplicate-check

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

MATEBALL-STORYBOOK
⚾ Storybook 배포가 완료되었습니다!

Copy link

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (5)
src/pages/sign-up/constants/validation.ts (1)

44-44: 상수 위치/이름 정리: API 레이어가 pages 상수에 의존하지 않도록 분리 + 재시도 의미 명확화

  • user-mutations.ts가 pages/sign-up/constants에 의존합니다. 공용 상수는 @constants(또는 @shared/constants)로 이동하는 게 적절합니다.
  • 현재 failureCount < 2라면 실질 재시도는 1회입니다. 의도가 “최대 2회 재시도”라면 비교식을 <=로, 아니면 상수명을 ...MAX_RETRIES = 1처럼 의미가 드러나게 바꾸는 걸 권장합니다.

적용 예시(이 파일 기준 변경):

- export const NICKNAME_DUPLICATE_FAILURE_COUNT = 2;
+ // 공용 위치(@constants/retry.ts 등)로 이동 권장
+ export const NICKNAME_CHECK_MAX_RETRIES = 1;

user-mutations.ts에서는 아래처럼 수정:

- import { NICKNAME_DUPLICATE_FAILURE_COUNT } from '@pages/sign-up/constants/validation';
+ import { NICKNAME_CHECK_MAX_RETRIES } from '@constants/retry';

- return failureCount < NICKNAME_DUPLICATE_FAILURE_COUNT;
+ return failureCount <= NICKNAME_CHECK_MAX_RETRIES;
src/pages/edit-profile/edit-profile.tsx (1)

61-61: 중복 확인 중 다중 클릭 방지: pending 상태 노출

isPending을 구조분해해 버튼 비활성화/스피너 연결 권장.

- const { mutateAsync: checkNickname } = useMutation(userMutations.NICKNAME_CHECK());
+ const {
+   mutateAsync: checkNickname,
+   isPending: isCheckingNickname,
+ } = useMutation(userMutations.NICKNAME_CHECK());

(변경은 선택 범위 밖이지만 참고) 버튼 비활성화 예:

<Button
  type="button"
  variant={
    !!errors.nickname || nicknameVal.trim().length === 0 || isSubmitting || isCheckingNickname
      ? 'disabled'
      : 'skyblue'
  }
  disabled={!!errors.nickname || nicknameVal.trim().length === 0 || isSubmitting || isCheckingNickname}
  aria-busy={isCheckingNickname}
  label="중복 확인"
  onClick={handleCheckNickname}
/>
src/pages/sign-up/components/signup-step.tsx (1)

55-55: 중복 확인 중 다중 클릭 방지: pending 상태 노출

isPending을 노출해 버튼 비활성화/로딩 상태에 사용하세요.

- const { mutateAsync: checkNickname } = useMutation(userMutations.NICKNAME_CHECK());
+ const {
+   mutateAsync: checkNickname,
+   isPending: isCheckingNickname,
+ } = useMutation(userMutations.NICKNAME_CHECK());

(선택 범위 밖 참고) “중복 확인” 버튼:

<Button
  label="중복 확인"
  type="button"
  onClick={handleCheckNickname}
  disabled={!isNicknameValid || isCheckingNickname}
  aria-busy={isCheckingNickname}
/>
src/shared/apis/user/user-mutations.ts (2)

75-77: mutationKey 추가로 디버깅/Devtools 가독성 개선

뮤테이션 키를 명시하면 추적이 수월합니다.

- NICKNAME_CHECK: () =>
-   mutationOptions<boolean, Error, { nickname: string }>({
+ NICKNAME_CHECK: () =>
+   mutationOptions<boolean, Error, { nickname: string }>({
+     mutationKey: ['user', 'nickname-check'],
      mutationFn: async ({ nickname }) => {

88-93: 재시도 정책 의도 확인 필요(비교식/상수명 불일치 가능성)

현재 로직은 “실패 1회까지 재시도 허용”입니다. 의도가 “최대 2회 재시도”라면 <=로 변경하거나, 의도가 1회 재시도라면 상수를 ...MAX_RETRIES = 1로 명확히 하세요. 또한 상수는 @constants 쪽으로 이동 권장(모듈 의존 방향).

- return failureCount < NICKNAME_DUPLICATE_FAILURE_COUNT;
+ // 예: 1회 재시도 의도라면 MAX_RETRIES = 1로 네이밍 변경 권장
+ return failureCount <= NICKNAME_CHECK_MAX_RETRIES;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3475d8f and 8a07a6d.

📒 Files selected for processing (5)
  • src/pages/edit-profile/edit-profile.tsx (2 hunks)
  • src/pages/sign-up/components/signup-step.tsx (3 hunks)
  • src/pages/sign-up/constants/validation.ts (1 hunks)
  • src/shared/apis/user/user-mutations.ts (2 hunks)
  • src/shared/apis/user/user-queries.ts (0 hunks)
💤 Files with no reviewable changes (1)
  • src/shared/apis/user/user-queries.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#95
File: src/pages/sign-up/components/nickname-step.tsx:28-30
Timestamp: 2025-07-09T18:07:41.693Z
Learning: heesunee는 src/pages/sign-up/components/nickname-step.tsx의 onSubmit 핸들러 API 호출을 쿼리와 함께 통합해서 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#78
File: src/pages/sign-up/utils/age-calculate.ts:1-4
Timestamp: 2025-07-09T09:08:46.371Z
Learning: heesunee는 나이 계산 시 한국식 나이 시스템을 사용하므로, 정확한 생일 기반 계산보다는 출생연도 기준의 간단한 계산 방식을 선호합니다.
📚 Learning: 2025-07-09T18:07:41.693Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#95
File: src/pages/sign-up/components/nickname-step.tsx:28-30
Timestamp: 2025-07-09T18:07:41.693Z
Learning: heesunee는 src/pages/sign-up/components/nickname-step.tsx의 onSubmit 핸들러 API 호출을 쿼리와 함께 통합해서 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.

Applied to files:

  • src/pages/edit-profile/edit-profile.tsx
  • src/pages/sign-up/components/signup-step.tsx
📚 Learning: 2025-07-09T17:24:19.755Z
Learnt from: heesunee
PR: MATEBALL/MATEBALL-CLIENT#94
File: src/pages/sign-up/sign-up.tsx:4-4
Timestamp: 2025-07-09T17:24:19.755Z
Learning: heesunee는 src/pages/sign-up/sign-up.tsx의 isNicknameStep 하드코딩된 값을 퍼널 패턴으로 카카오 통합과 함께 나중에 처리할 예정이므로, 이 부분에 대해 다시 언급하지 않아야 합니다.

Applied to files:

  • src/pages/sign-up/components/signup-step.tsx
🧬 Code graph analysis (3)
src/pages/edit-profile/edit-profile.tsx (1)
src/shared/apis/user/user-mutations.ts (1)
  • userMutations (20-95)
src/shared/apis/user/user-mutations.ts (4)
src/shared/apis/base/http.ts (1)
  • get (3-5)
src/shared/constants/api.ts (1)
  • END_POINT (3-52)
src/shared/constants/response.ts (1)
  • HTTP_STATUS (1-11)
src/pages/sign-up/constants/validation.ts (1)
  • NICKNAME_DUPLICATE_FAILURE_COUNT (44-44)
src/pages/sign-up/components/signup-step.tsx (1)
src/shared/apis/user/user-mutations.ts (1)
  • userMutations (20-95)
🔇 Additional comments (1)
src/pages/sign-up/components/signup-step.tsx (1)

23-23: LGTM: useMutation 도입

쿼리→뮤테이션 전환 방향성 적절합니다.

Comment on lines +115 to 117
const available = await checkNickname({ nickname: nicknameVal });
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

경합(race) 조건: 응답 도착 시 닉네임이 바뀐 경우 오표시 가능

요청 시점의 닉네임과 응답 시점의 현재 값이 다르면 상태 업데이트를 스킵해야 합니다. 또한 공백은 트림해서 전송을 권장합니다.

- const available = await checkNickname({ nickname: nicknameVal });
- setNicknameStatus(available ? 'available' : 'duplicate');
+ const requested = nicknameVal.trim();
+ const available = await checkNickname({ nickname: requested });
+ // 사용자가 입력을 바꿨다면 이전 응답은 무시
+ if (requested !== watch('nickname').trim()) return;
+ setNicknameStatus(available ? 'available' : 'duplicate');
📝 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 available = await checkNickname({ nickname: nicknameVal });
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
const requested = nicknameVal.trim();
const available = await checkNickname({ nickname: requested });
// 사용자가 입력을 바꿨다면 이전 응답은 무시
if (requested !== watch('nickname').trim()) return;
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
🤖 Prompt for AI Agents
In src/pages/edit-profile/edit-profile.tsx around lines 115 to 117, the nickname
availability check can race: if the user changes the nickname before the async
response arrives the component may set the wrong status. Trim the nickname
before sending (const trimmed = nicknameVal.trim()) and call checkNickname({
nickname: trimmed }); after the await, compare the latest current nickname state
(or input value) with the trimmed value used for the request and only call
setNicknameStatus(available ? 'available' : 'duplicate') if they match; also
ensure the catch block does not swallow this logic (only handle/report the
error) and does not update state when the names differ.

Comment on lines +105 to 107
const available = await checkNickname({ nickname: nicknameValue });
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

경합(race) 조건: 응답/입력 불일치 시 잘못된 상태 표시 가능

요청 당시 닉네임을 캡처해 비교하세요. 공백 트림도 권장.

- const available = await checkNickname({ nickname: nicknameValue });
- setNicknameStatus(available ? 'available' : 'duplicate');
+ const requested = nicknameValue.trim();
+ const available = await checkNickname({ nickname: requested });
+ if (requested !== (watch('nickname') ?? '').trim()) return;
+ setNicknameStatus(available ? 'available' : 'duplicate');
📝 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 available = await checkNickname({ nickname: nicknameValue });
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
const requested = nicknameValue.trim();
const available = await checkNickname({ nickname: requested });
if (requested !== (watch('nickname') ?? '').trim()) return;
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
🤖 Prompt for AI Agents
In src/pages/sign-up/components/signup-step.tsx around lines 105 to 107, the
nickname availability result may be applied to stale input if the user changed
the field before the async check returned; capture and trim the current nickname
into a local variable before calling checkNickname, then after awaiting the
response verify that the field's current trimmed value still matches that
captured value before calling setNicknameStatus(available ? 'available' :
'duplicate'); this ensures you only update status for the nickname that was
actually checked.

@heesunee heesunee merged commit d761b48 into develop Nov 19, 2025
7 checks passed
@heesunee heesunee deleted the refactor/#393/duplicate-check branch November 19, 2025 05:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: 중복확인 로직을 useQuery에서 useMutation으로 수정합니다.

1 participant