Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions src/pages/edit-profile/edit-profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ const EditProfile = () => {
const nicknameVal = watch('nickname', '');
const introductionVal = watch('introduction', '');

const { refetch: refetchNicknameCheck } = useQuery(userQueries.NICKNAME_CHECK(nicknameVal));

const { mutateAsync: checkNickname } = useMutation(userMutations.NICKNAME_CHECK());
const submitNickname = async () => {
const ok = await trigger('nickname');
if (!ok) return;
Expand Down Expand Up @@ -113,8 +112,8 @@ const EditProfile = () => {
if (errors.nickname || nicknameVal.trim().length < 2) return;
setNicknameStatus('checking');
try {
const { data } = await refetchNicknameCheck();
setNicknameStatus(data ? 'available' : 'duplicate');
const available = await checkNickname({ nickname: nicknameVal });
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
Comment on lines +115 to 117
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.

setNicknameStatus('idle');
}
Expand Down
9 changes: 4 additions & 5 deletions src/pages/sign-up/components/signup-step.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { userMutations } from '@apis/user/user-mutations';
import { userQueries } from '@apis/user/user-queries';
import Button from '@components/button/button/button';
import Input from '@components/input/input';
import { zodResolver } from '@hookform/resolvers/zod';
Expand All @@ -21,7 +20,7 @@ import {
} from '@pages/sign-up/constants/validation';
import { type UserInfoFormValues, UserInfoSchema } from '@pages/sign-up/schema/validation-schema';
import type { NicknameStatus } from '@pages/sign-up/types/nickname-types';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useMutation } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import type { postUserInfoRequest } from '@/shared/types/user-types';
Expand Down Expand Up @@ -52,7 +51,7 @@ const SignupStep = () => {

const userInfoMutation = useMutation(userMutations.USER_INFO());
const agreementInfoMutaion = useMutation(userMutations.AGREEMENT_INFO());
const { refetch: refetchNicknameCheck } = useQuery(userQueries.NICKNAME_CHECK(nicknameValue));
const { mutateAsync: checkNickname } = useMutation(userMutations.NICKNAME_CHECK());

const informationLength = informationValue.length ?? 0;

Expand Down Expand Up @@ -103,8 +102,8 @@ const SignupStep = () => {
if (!isNicknameValid) return;
setNicknameStatus('checking');
try {
const { data } = await refetchNicknameCheck();
setNicknameStatus(data ? 'available' : 'duplicate');
const available = await checkNickname({ nickname: nicknameValue });
setNicknameStatus(available ? 'available' : 'duplicate');
} catch {
Comment on lines +105 to 107
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.

setNicknameStatus('idle');
}
Expand Down
2 changes: 2 additions & 0 deletions src/pages/sign-up/constants/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ export const SIGNUP_STEPS = ['AGREEMENT', 'INFORMATION'];

export const INTRODUCTION_MIN_LENGTH = 1;
export const INTRODUCTION_MAX_LENGTH = 50;

export const NICKNAME_DUPLICATE_FAILURE_COUNT = 2;
26 changes: 25 additions & 1 deletion src/shared/apis/user/user-mutations.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { patch, post, put } from '@apis/base/http';
import { get, patch, post, put } from '@apis/base/http';
import { END_POINT } from '@constants/api';
import { USER_KEY } from '@constants/query-key';
import { HTTP_STATUS } from '@constants/response';
import queryClient from '@libs/query-client';
import { NICKNAME_DUPLICATE_FAILURE_COUNT } from '@pages/sign-up/constants/validation';
import { router } from '@routes/router';
import { ROUTES } from '@routes/routes-config';
import { mutationOptions } from '@tanstack/react-query';
import axios from 'axios';
import type { responseTypes } from '@/shared/types/base-types';
import type {
postAgreementInfoRequest,
Expand Down Expand Up @@ -68,4 +71,25 @@ export const userMutations = {
mutationKey: USER_KEY.AGREEMENT(),
mutationFn: ({ hasAccepted }) => post(END_POINT.AGREEMENT_INFO, { hasAccepted }),
}),

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;
},
}),
};
24 changes: 0 additions & 24 deletions src/shared/apis/user/user-queries.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { get } from '@apis/base/http';
import { END_POINT } from '@constants/api';
import { USER_KEY } from '@constants/query-key';
import { HTTP_STATUS } from '@constants/response';
import { queryOptions } from '@tanstack/react-query';
import axios from 'axios';
import type { getMatchConditionResponse, getUserInfoResponse } from '@/shared/types/user-types';

export const userQueries = {
Expand All @@ -26,26 +24,4 @@ export const userQueries = {
queryKey: USER_KEY.MATCH_CONDITION(),
queryFn: () => get<getMatchConditionResponse>(END_POINT.MATCH_CONDITION),
}),

NICKNAME_CHECK: (nickname: string) =>
queryOptions<boolean>({
queryKey: USER_KEY.NICKNAME_CHECK(nickname),
enabled: false,
queryFn: async () => {
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 < 2;
},
}),
};