[ 인턴 4주차 ] 전체 input을 react-hook-form로 리팩토링

Innes·2025년 1월 19일
0

인턴

목록 보기
13/14

✨ react-hook-form 리팩토링

👩🏻‍💻 '비밀번호 찾기' 페이지 리팩토링

자꾸 input에서 autocomplete 경고 메시지가 떴다.
autocomplete 은 자동완성이었다..!
반드시 있어야 하는 것은 아니지만, 최적의 UX와 보안을 위해 추가하는 것이 권장됨.
특히, 다음 경우에는 autocomplete 속성을 명확하게 지정하는 것이 좋아:

  • 필수적인 경우:
    • 비밀번호 필드 (autocomplete="current-password" 또는 autocomplete="new-password")
    • 로그인 ID, 이메일 필드 (autocomplete="username", autocomplete="email")

  • 불필요한 경우:
    • 보안이 중요한 필드에서 자동완성을 막고 싶을 때 (autocomplete="off")
    • 일반 텍스트 입력 필드에서 자동완성이 필요하지 않을 때

  • autocomplete 이름은 id와 같아야하나? 뭘 지정해줘야하는거지?
    -> 브라우저는 autocomplete이름 자체를 가지고 자동완성할 내용들을 분류해서 저장함.
    브라우저는 autocomplete 값을 기준으로 저장하고 구분하지, name 값이 같다고 해서 하나로 묶지는 않아! 🚀
    자동완성을 정확하게 활용하려면 autocomplete 값을 명확하게 지정하는 것이 중요함! 🚀

✅ 브라우저는 기본적으로 name 값을 기준으로 자동완성 데이터를 분류하지만,
✅ 자동완성을 적용할 때는 autocomplete 속성이 있으면 이를 우선 적용해.

  • 그럼 id랑 name은 꼭 있어야하나?
    id는 선택사항
    CSS/JS, label 태그와 연결할 때 id를 사용하면 UX가 향상됨.

name은 꼭 있어야함
폼 제출 시 (<form> 사용 시): name이 있어야 서버로 데이터를 전송할 수 있음.
브라우저 자동완성 기능: name이 있어야 브라우저가 입력 값을 저장하고 자동완성을 제공할 수 있음.
백엔드와 연동 시: API 요청에서 name 값이 필드 이름으로 사용됨.

  • input에 name넣었더니 에러 : 'name'이(가) 두 번 이상 지정되어 이 사용량을 덮어씁니다.ts(2783)

->
<input> 요소에서 name 속성을 JSX에서 직접 지정(name={id})하면서, 동시에 register(id, ...)을 사용하고 있습니다. react-hook-form의 register 함수는 내부적으로 name을 자동으로 할당하므로, JSX에서 name을 별도로 지정하면 중복 선언 오류가 발생합니다.
해결 방법:
name={id}를 제거하면 됩니다.
✅ auto complete props에 추가하기


👩🏻‍💻 '내정보 수정' 페이지

📝 로직 정리

[ ☑️ 로직 요약 ]
유저 정보 샘플 데이터 넣어놓기
유저 정보 가져와서 기본값 보여주기


[ ☑️ 로직 디테일 ]
1. 로그인할때 유저정보도 백엔드에서 같이 가져오기
2. zustand에 저장해두기
3. 내정보 조회, 내정보 수정 - zustand에 있는 정보를 가져오기
4. 수정시 - zustand내용 수정, 백엔드 유저정보 POST(or UPDATE)로 수정
(zustand내용 수정하는 set하는 로직 안에 유저정보 POST하는 api로직도 넣어놓는것도 방법이겠군)

-> 백엔드 없이 임시 로직 구현해두려면
일단 샘플 유저데이터를 zustand에만 넣어놓으면 되겠다

✅ 내 정보 수정 페이지에 접근하면 유저의 기존 정보를 input에 default value로 보여주고싶었다.

그런데 react hook form의 default Value를 useForm에서 가져온다해도,
zustand에 있는 값을 최초로 가져온 default value가 캐시에 저장되듯 ‘최초 렌더링 시에만 반영’된다.

그래서 zustand에 있는 유저정보를 변경하더라도 zustand에 있는 업데이트된 값을 default value로 보여주는게 아니라
최초로 가져왔던 값을 그대로 보여준다.

그래서 useEffect를 사용하여 reset() 하는게 필요하다!

// Zustand에서 가져온 값으로 초기화 (내정보 수정 페이지 처음 진입 시 한 번만 실행) 
  useEffect(() => { 
  reset({ name, email, phone, companyName, }); 
  }, [name, email, phone, companyName, reset]);

( useFormContext에서 가져오는 정확한 메서드 이름은 getValues 이다. )

getValues 사용 방법은

  <input defaultValue={getValues("name")} />
  • react hook form의 watch는 input의 name을 기준으로 가져와 조회하는데, register가 name을 자동으로 관리하기 때문에 input에서 직접 name을 관리할 필요 없음!
    (register의 첫번째 인자가 name이 된다.)

📝 내정보 수정 페이지 미해결 부분

☑️ zustand에서 가져온 데이터에서 그대로 input에서 수정필요
( input에 보여주고있는 기존 유저데이터를 수정하면 watch 에 감지가 안됨, submit버튼 클릭 후에야 newName이 감지되기 시작함 )

-> ✨ 해결

  • 기존 :
  const { name, email, phone, companyName, isDocApproved }: UserInfo =
    useUserInfoStore();

<LabelInputSet
            labelText="이메일 주소"
            id="email"
            width="292px"
            defaultValue={email}
          />

이렇게 defaultValue에 zustand에서 가져온 값을 그대로 넣어주고있었음.
-> 현재 watch("name")이 기본적으로 zustand에서 받아온 값이 아니라 react-hook-form 내부 상태만 감시하고 있어, 처음에는 값이 비어 있을 가능성이 큼.
-> defaultValue 대신 setValue를 사용하여 초기값을 강제 설정.

  • 해결 :
  const { getValues, setValue, reset, watch } = useFormContext();
  const newName = watch("name");
  console.log("newName", newName);

  // 초기값 강제 설정
  useEffect(() => {
    setValue("name", name);
    setValue("email", email);
    setValue("phone", phone);
    setValue("companyName", companyName);
  }, [name, email, phone, companyName, setValue]);

  <LabelInputSet
          labelText="이름"
          id="name"
          width="100%"
          defaultValue={getValues("name")}
// defaultValue={name} 도 상관없음 
//(초기값 보여주는것 뿐이라서 zustand값 그대로 보여줘도 됨)
        />

-> input에 기존 유저정보를 기본값으로도 보여주고,
해당 텍스트를 수정하면 watch로 바로 console창에 감지도 된다.

☑️ 제출시 수정된 내용만 zustand 수정

  • 문제 : 수정하려면 handleSubmit 함수에서 getValues, setValue가 필요했음. (useForm의 method)
    -> CommonForm 안에서 useForm을 선언해서 methods를 FormProvider에 내려주고있기 때문에

EditUserInfo.tsx같이 CommonForm을 사용하는 사용처에서는 getValues, setValue를 사용하지 못해 난감했다.

handleSubmit에는 methods가 필요한데, handleSubmit 함수가 있는 컴포넌트 안에서 CommonForm을 사용하고 있으니

// AccountPage.tsx

const hondleSubmit = ()=>{
// methods 필요
}

<CommonForm>
<EditUserInfo/>
</CommonForm>

CommonForm 안에 Provider, methods가 다 들어있어서
AccountPage 안에서는 getValues, setValue같은 methods를 쓸수가 없었다!!!
그럼 handleSubmit함수는 어떻게 작성하라고… ㅜㅜ

  • ✨ 해결 : methods를 CommonForm 사용처에서 직접 선언
    const methods = useForm()
    그리고 methods를 CommonForm props로 전달해버리기!!
    -> CommonForm 안에 있는 EditUserInfoContent.tsx 안에서도 methods, useFormContext 등 사용 가능하다!!!

📝 CommonForm 구조 정리

⭐️ 구조 정리

// AccountPage.tsx
<EditUserInfo/>

EditUserInfo.tsx
const methods = useForm()
<CommonForm>
<EditUserInfoContent/>
</CommonForm>

->

EditUserInfoContent.tsx
useFormContext() 사용 가능


👩🏻‍💻✨ '회원가입' 페이지

📝 회원가입 페이지 구성

  • 회원가입 페이지는 SignupPage 컴포넌트를 기준으로 step1~4 컴포넌트가 동적 라우팅으로 연결되어 있다.

    • step1 : 이름, 이메일
    • step2 : 아이디, 비밀번호, 비밀번호 확인
    • step3 : 전화번호
    • step4 : 회사명, ( 사업자등록증 pdf )
  • step1 ~ 4 모두 url 다름
    하지만 입력된 정보들을 한번에 모아서 백엔드 회원가입 api에 제출할것.

  • 방법 :

const methods = useForm()

<CommonForm methods={methods}>
<Step1>
<Step2></CommonForm>

💬 질문거리

  • url이 다 다른데도 useForm을 부모에서 관리하면 한데 모을수가 있나?
    (다음 페이지로 이동하면 새로고침될텐데 그래도 이전 페이지에서 입력한 input값을 useForm이 들고있는게 맞나?)

-> useForm은 컴포넌트 언마운트, 리렌더링시 초기화됨
step1~4는 다른 url이기때문에 페이지 이동하면 CommonForm은 언마운트됨.
-> useForm에 입력된 이전 값은 초기화됨.
-> zustand로 관리 필요 (zustand값은 새로고침하지 않는 이상 리렌더링, 언마운트되어도 상태가 유지되기 때문!)
(새로고침해도 유지하고싶으면 persist와 로컬스토리지를 사용할수있다.)

  • api연결은 나중에 하고, 회원가입 데이터들을 한번에 제출하는게 정상 동작하는지만 handleSubmit 실행시 콘솔찍어서 제출데이터를 확인하고싶어.

  • 추후 api연결시 handleSubmit함수에서 회원가입api에 제출하는 api 추가만 하면 되는게 맞는지?

=>

‼ 최종 로직 계획

  1. zustand에 유저 정보 저장 (stepData, updateStepData)
  2. 각 step에서 zustand에 입력값 저장, 다음스텝으로 페이지이동 처리
  3. 최종페이지 : zustand에서 stepData가져오기, onSubmit 함수에서 회원가입 api 전달
    (지금은 일단 콘솔만)

-> CommonForm을 회원가입페이지에서 감싸는데
최종제출 로직 함수는 step4에서 실행되어야함.

-> CommonForm에서 최종 실행할 함수 작성해서 onSubmit안에 넣어놓기.
step4에서는 useFormContext에서 handleSubmit을 가져와서
최종제출 클릭시 버튼에 handleSubmit을 onClick으로 추가
(원래는 handleSubmit은 콜백함수를 인자로 받는게 필수이지만,
이미 그 처리는 CommonForm에서 하고있기 때문에

step4에서는 그냥 onClick={handleSubmit}만 해도 괜찮음

근데 다른 로직도 클릭시 추가하고 싶다면 그때는
onClick={handleSubmit(() => openModal(MODAL_IDS.SIGNUP_COMPLETE))}
이런식으로 handleSubmit안에 콜백함수로 넣어주면 됨

  • 각 step에서 useEffect로 setValue해주는게 필요한 이유
    -> 이전 페이지로 이동시 이전 step에서 입력했던 데이터가 input에 그대로 들어있어야하기 때문!

회원가입을 이렇게 step별로 나눠서 하는걸
‘멀티 스텝 폼 Multi-Step Form’ 방식이라고 하는구나 신기하당

반대는 단일 폼 방식(One-Page Form)

profile
무서운 속도로 흡수하는 스펀지 개발자 🧽

0개의 댓글