[SoundLink] 복잡한 회원가입 폼, useRef → useState로 리팩토링

강수영·2025년 7월 11일
0

초기에는 회원가입 폼 데이터를 ref로 관리해 불필요한 리렌더링을 방지하려고 했습니다.

하지만 회원가입 버튼 활성화 여부를 판단하기 위해 각 입력 필드의 유효성 상태는 useState로 따로 관리했습니다.

// 사용자 입력 데이터를 저장하는 ref
const formDataRef = useRef({
  id: '',
  password: '',
  nickname: '',
  email: '',
});

// 각 입력 필드의 유효성 상태를 관리하는 state
const [validity, setValidity] = useState({
  id: false,
  password: false,
  passwordConfirm: false,
  nickname: false,
  email: false,
  authcode: false,
});

⚠️ 문제 상황: 복잡해지는 상태 관리

유효성 상태를 useState로 객체 형태로 관리했는데, 객체는 참조값을 기준으로 비교되기 때문에 내부 값이 같더라도 매번 다른 값으로 인식되어 불필요한 리렌더링이 발생했습니다.

이를 방지하기 위해 값이 실제로 변경되지 않은 경우에는 리렌더링을 유발하지 않도록 비교 함수를 따로 작성해야 했습니다.

// validity 업데이트 함수
const updateValidity = (key, value) => {
  setValidity((prev) => {
    if (prev[key] === value) return prev; // 값이 동일하면 변경 X
    return { ...prev, [key]: value };
  });
};

여러 개의 입력 필드 컴포넌트에 ref, state, 유효성 업데이트 함수 등을 모두 전달하다 보니, props가 많아지고 구조가 점점 복잡해졌습니다.


간소화 시도: FormData API

복잡한 상태 관리를 줄이기 위해 FormData API를 사용해 입력값을 제출 시점에 한 번에 처리하는 방식을 고려했습니다.

모든 입력값을 각각 상태로 관리하지 않고, <input> 태그의 name 속성을 활용해 제출 시점에 한 번에 데이터를 사용하려고했습니다.

하지만 이 방식은 제출 시점에 정확히 어떤 값이 사용되는지 명확하게 추적하기 어려웠고, React의 선언적 데이터 흐름과도 잘 맞지 않는다고 판단했습니다.


useRef에서 useState로의 구조 변경

처음엔 validity 값이 모두 true일 때 회원가입 버튼을 활성화하는 방식으로 구현했는데, 생각해보니 입력값 자체를 각각 useState로 관리하고, 모든 값이 존재할 때 버튼을 활성화하는 방식이 더 깔끔하다고 생각했습니다.

그래서 각 컴포넌트 내부에서 유효성 검사를 마친 뒤, 상위 폼 상태를 업데이트하는 구조로 변경했습니다.

// 입력값 상태 관리 (각 필드별 useState)
const [id, setId] = useState('');
const [password, setPassword] = useState('');
const [email, setEmail] = useState('');
const [nickname, setNickname] = useState('');

// 각 입력 컴포넌트에서 유효성 검사 후, 상위 폼 상태를 업데이트 
<IdInput onChange={setId} />
<PasswordGroupSection onChange={setPassword} />
<NicknameInput onChange={setNickname} />
<EmailGroupSection onChange={setEmail} email={email} />

아이디와 닉네임은 중복 확인 버튼을 눌러 성공했을 때 해당 입력값을 상위 상태에 업데이트했습니다. 그리고 사용자가 입력값을 수정하면 중복 확인 결과는 무효화되므로, 해당 상태를 빈 문자열로 초기화하는 방식으로 처리했습니다.

비밀번호와 비밀번호 확인은 하나의 컴포넌트로 묶어서 관리했습니다. 컴포넌트 내부에 passwordpasswordConfirm이라는 임시 상태를 각각 두고, 각 입력값을 관리하다가 두 값이 일치할 때에만 상위 상태를 업데이트하도록 설계했습니다.

이메일 인증도 인증번호 입력과 함께 하나의 컴포넌트로 묶어서, 비밀번호와 비슷한 방식으로 구현했습니다.


🚀 정리

결과적으로 ref + state 구조를 모두 state 기반으로 통합하면서, 불필요한 props 전달이 줄었고, 관리해야 할 상태도 훨씬 간단해졌습니다.

이번 회원가입 폼 리팩토링을 통해 refstate의 차이를 직접 체감하며 고민할 수 있었고, 그 과정에서 제어 컴포넌트와 비제어 컴포넌트의 개념도 자연스럽게 정리되었습니다.

또한 FormData 방식도 시도해보며, 장단점을 직접 경험해볼 수 있었던 좋은 기회였습니다.

profile
프론트엔드 개발자

0개의 댓글