inkyu0103 / badminton-app

https://staging-mobae.xyz
1 stars 0 forks source link

feat: add nickname #117

Closed inkyu0103 closed 1 year ago

inkyu0103 commented 1 year ago

설명

유니크한 사용자 별명을 입력받도록 변경합니다.

관련 이슈

Fix #116

변경사항

BE

이와 별도로 테이블 네이밍이 모두 대문자로 되어있어, 소문자로 변경하였습니다.

FE

스크린샷

vercel[bot] commented 1 year ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
badminton-app ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 23, 2023 6:57am
inkyu0103 commented 1 year ago

debounce 적용이 안되는 문제 있는데, 어떻게 해결해야할지 감도 안잡힙니다.

inkyu0103 commented 1 year ago

목표

부딪힌 문제들

1. react-query 사용 여부

프로젝트에서 기본적으로 react-query를 사용하고 있기 때문에, isUsableNickname도 custom hook으로 감싸서 사용하고자 했다.

그러면, 다음과 같은 구조를 가지게 된다.

const SignupForms = () => {
   const { ... } = useUsableNickname(...)
   ...
   return <SignupFormsView .../>
}

현재 SignupForms에 필요한 비동기 처리 로직은 SignupForms에서 정의하고 있고, Form과 관련된 내용은 SignupFormsView에서 처리하고 있다.

useUsableNickname 의 인자로 nickname을 받아와야만 검증이 가능한데 현재 구조로는 불가능한 상황.

1번 문제 해결 방안

1. SignupFormsView에 있는 Form과 관련된 로직들을 SignupForms로 올리는 방법

const SignupForms = () => {
   const methods = useForm();
   const { data : isUsableNickname } = useUsableNickname(methods.watch('nickname'));
   ...
   return <FormProvider><SignupFormsView isUsableNickname = {isUsableNickname} .../></FormProvider>

이렇게 하면, useQuery를 사용하여 모든 api를 react-query를 사용하도록 할 수 있다.
사용가능한 닉네임인지 여부를 props로 넘겨주어야 하는 번거로움이 있다. 개인적으로 form과 관련된 내용은 view에서 처리하면 좋겠어서, 굳이 form 관련 로직을 올리지는 않았다. (그리고 애초에 react-query 사용과 무관하게 debounce 문제는 해결되지 않는다.)

2. 기존 debounce의 한계

기존 debounce 함수는 다음과 같다.

export const debounce = (func: any, time = 200) => {
  let timer: ReturnType<typeof setTimeout>;

  return (...args: unknown[]) => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => func(...args), time);
  };
};

문제는 어떤 값을 반환하는 함수를 debounce로 감싸게 되면, 그 값을 사용할 수가 없게 된다.

const handleValidateNickname = debouce((nickname) => isUsableNickname(nickname) , 200)

<input
  className="w-full py-2 text-sm rounded-md shadow-md outline-none indent-2 "
  placeholder="사용하실 별명을 입력해주세요"
  {...register("nickname", {
    required: {
    value: true,
    message: "별명을 입력해주세요",
  },
  validate: {
    isUsableNickname: async (nickname) =>(await handleValidateNickname(nickname)) || "이미 사용중인 별명입니다.",
    },
  })}
/>

해당 코드로 validate를 실행하게 되면, 항상 이미 사용중인 별명입니다 를 볼 수 있다. debounce 함수 자체에서 아무런 값도 반환하지 않기 때문에, 암시적으로 undefined를 반환하게 되고, falsy한 값이므로 || 연산자에 의해 두 번째 값으로 넘어가게 된다.

다음과 같은 방식으로 debounce를 수정하였다. 다만, 기존에 사용하던 debounce와 호환이 되는지 여부를 확인해야해서 extractDebounce.ts 라는 파일에 작성했다.

export const extractDebounce = (func: any, time = 200) => {
  let timer: ReturnType<typeof setTimeout>;

  return (...args: unknown[]) => {
    if (timer) {
      clearTimeout(timer);
    }

    return new Promise((resolve, reject) => {
      timer = setTimeout(async () => {
        try {
          const result = await func(...args);
          resolve(result);
        } catch (error) {
          reject(error);
        }
      }, time);
    });
  };
};

기존 debounce와 다른 점은, Promise를 리턴한다는 점이다. 그렇게 되면 외부에서 await 키워드와 함께 사용하면, 원하는 값을 받을 수 있게된다.

const handleValidateNickname = extractDebounce((nickname) => isUsableNickname(nickname) , 200)

<input
  className="w-full py-2 text-sm rounded-md shadow-md outline-none indent-2 "
  placeholder="사용하실 별명을 입력해주세요"
  {...register("nickname", {
    required: {
    value: true,
    message: "별명을 입력해주세요",
  },
  validate: {
    isUsableNickname: async (nickname) =>(await handleValidateNickname(nickname)) || "이미 사용중인 별명입니다.",
    },
  })}
/>