훅은 개별적인 문제를 우아하게 해결했지만, 그 문제들이 한 곳에 모였을 때 새로운 종류의 복잡성이 고개를 들기 시작했다.
페이스북의 한 팀이 복잡한 설정 페이지를 개발하고 있었다. 그 페이지에는 수십 개의 입력 필드와 토글 스위치, 드롭다운 메뉴가 있었다. 사용자의 프로필 정보, 알림 설정, 개인정보 보호 설정 등 다양한 상태를 한 번에 관리해야 했다.
개발자는 배운 대로 useState를 사용하여 각 필드의 상태를 관리하기 시작했다.
function SettingsForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [bio, setBio] = useState('');
const [emailNotifications, setEmailNotifications] = useState(true);
const [pushNotifications, setPushNotifications] = useState(false);
const [profileVisibility, setProfileVisibility] = useState('public');
const [contactPrivacy, setContactPrivacy] = useState('friends_only');
// ... 로딩 상태, 에러 상태, 저장 상태 등등 ...
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
const [isSaved, setIsSaved] = useState(false);
// ...
}
useState 호출이 열 개를 넘어가자, 개발자는 미간을 찌푸렸다.
컴포넌트의 상단은 상태 선언문으로 가득 차, 실제 렌더링 로직을 한눈에 파악하기 어려웠다.
하지만 진짜 문제는 상태를 업데이트하는 로직에서 발생했다.
‘모든 설정 초기화’ 버튼을 구현해야 하는 상황이었다. 이 버튼을 누르면 모든 상태를 초기값으로 되돌려야 했다.
const handleReset = () => {
setName('');
setEmail('');
setBio('');
setEmailNotifications(true);
setPushNotifications(false);
setProfileVisibility('public');
// ... 모든 set 함수를 일일이 호출 ...
};
상태를 업데이트하는 set 함수 호출이 열 줄 가까이 반복되었다. 이 로직은 장황하고, 실수를 유발하기 쉬웠다. 만약 나중에 새로운 설정이 추가된다면, 개발자는 handleReset 함수를 잊지 않고 수정해야만 했다.
더 복잡한 시나리오는 ‘저장’ 버튼을 눌렀을 때였다.
저장 로직은 여러 상태를 동시에 업데이트해야 했다.
const handleSave = () => {
setIsLoading(true);
setError(null);
setIsSaved(false);
saveSettingsAPI({ name, email, ... })
.then(() => {
setIsLoading(false);
setIsSaved(true);
})
.catch(err => {
setIsLoading(false);
setError(err);
});
};
setIsLoading, setError, setIsSaved…
서로 관련 있는 상태 업데이트 로직이 하나의 이벤트 핸들러 안에 흩어져 있었다. 컴포넌트가 커질수록, 이런 핸들러 함수들은 점점 더 비대해지고 이해하기 어려워졌다. 어떤 동작이 어떤 상태들을 변화시키는지 한눈에 파악하는 것이 거의 불가능했다.
댄은 이 코드를 리뷰하며 useState의 명백한 한계를 보았다.
useState는 서로 독립적인, 단일 값의 상태를 관리하는 데에는 훌륭했다. 하지만 여러 상태가 서로 유기적으로 얽혀, 복잡한 로직에 따라 함께 변화해야 하는 경우에는 오히려 복잡성을 증가시켰다.
마치 오케스트라의 여러 악기들이 각자 연주하는 것과 같았다. 각 연주자는 훌륭했지만, 그들을 조화롭게 이끌 지휘자가 없었다.
개발자들에게는 이 흩어진 상태들을 하나로 묶고, 상태 변경 로직을 한 곳에서 체계적으로 관리할 수 있는, 더 강력한 도구가 필요했다.
리액트 팀은 이 문제를 해결하기 위해, 이미 자신들의 무기고에 준비해 둔 또 다른 훅을 꺼내 들었다.
그것은 댄에게는 너무나도 익숙한, Redux의 철학을 품고 있는 훅이었다.
그 훅의 이름은 useReducer였다.


