회원가입 플로우를 구현하고있다.
유저가 Input에 이름과 전화번호 데이터를 입력하면 해당 값이 SessionStorage에 저장되며 간편 본인인증을 실행한다.
본인인증이 success된다면 페이지가 리다이렉트 되는데, 리다이렉트 된 페이지에서 사용자가 입력하여 저장된 SessionStorage의 Data가 useForm의 defaultValues로 출력되도록 구현하고자 했다.

본인 인증을 거치고 다시 리다이렉트 되었을 때 params를 읽어와서 state로 관리하는 다음 이동 버튼은 활성화가 되지만, 이름과 휴대폰 전화를 입력받는 input에 defaultValues 값으로 SessionStorage value값이 지정되지 않는 문제가를 마주했다...
const { name, setSignupData } = useSignupStore();
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const [isActive, setIsActive] = useState(false);
const storedData = SessionStorage.getItem('signup-storage');
const parsedData = storedData ? JSON.parse(storedData) : null;
const storageName = parsedData?.state?.name || '';
const storagePhone = parsedData?.state?.phone || '';
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
defaultValues: {
name: storageName,
phone: storagePhone,
},
});
useEffect(() => {
if (searchParams.get('success')) {
setIsActive(true);
setSignupData({ name: storageName, phone: storagePhone });
}
}, []);
코드를 보고 react-hook-form을 잘못 사용 하고 있는 것 같다는 생각이 들었다!그래서 react-hook-form 공식 문서를 찾아보니 reset 함수를 추가했어야 했던 실수를 발견!
reset 함수는 react-hook-form에서 폼을 초기화하거나 다시 설정할 때 사용된다. reset을 사용해야 하는 이유는 주로 폼의 초기값을 외부 데이터로 설정하거나, 특정 상황에서 폼 데이터를 새롭게 갱신해야 할 때 필요한것!
그래서 나는 reset 함수를 사용하지 않았기 때문에 갱신이 되지 않고 있었던것! 공식문서를 꼼꼼히 읽어보지 않은 나의 잘못..
reset 함수는 외부 데이터가 변할 때 (ex. SessionStorage의 값이 변경되었을 때) react-hook-form의 상태를 수동으로 갱신해주는데 만약 reset을 사용하지 않으면, 외부 데이터가 업데이트되더라도 폼 값은 그대로 유지가 되는 문제가 발생한다. ( 바로 내 코드의 문제다! )
const {
register,
handleSubmit,
formState: { errors },
reset // reset 추가
} = useForm({
defaultValues: {
name: storageName,
phone: storagePhone,
},
});
useEffect(() => {
if (searchParams.get('success')) {
setIsActive(true);
setSignupData({ name: storageName, phone: storagePhone });
reset() // reset 함수 실행
}
}, []);
이렇게 reset 함수를 실행 해 주었다!
근데 함수를 실행 했음에도 불구하고 defaultValues 값은 적용되지 않았다.. 난 슬플 때 춤을 춰....
그래서 눈에 불을 켜고 온 신경을 집중하여 실행 화면을 뜯어보았다!!

useEffect.. 화면에 그려지고 왜 다시 리렌더링 되는거지?
새로고침을 하였을 때 defaultValues로 지정되었던 값이 잠깐 보였다가
깜빡이는 현상과 함께 defaultValues 출력이 휘발되는것을 파악했다.
왜 한번 더 깜빡이는 현상이 발생하는걸까?
이걸 파악하기 위해 코드에 console 출력을 하나하나 달아보았다
const {
register,
handleSubmit,
reset,
formState: { errors },
} = useForm({
defaultValues: {
name: storageName,
phone: storagePhone,
},
});
useEffect(() => {
console.log('useEffect1');
if (searchParams.get('success')) {
console.log('useEffect > success');
setIsActive(true);
setSignupData({ name: storageName, phone: storagePhone });
reset();
}
console.log('useEffect2');
}, []);
useEffect(() => {
console.log('isActive');
}, [isActive]);
useEffect(() => {
console.log('name');
}, [name]);

입력한 console 값은 잘 출력된다.
위에 작성된 코드에서 도달하지 못하는 부분은 없는것으로 판단! 사용자가 입력한 값이 SessionStorage에 저장된것도 확인이 되며 console에 출력도 된다.
defaultValues가 적용 되었다가 깜빡이는 현상과 함께 휘발되는것인가..?조금 더 공부해보니 useEffect의 실행 순서와 관련된 캡쳐문제라는것을 알았다
React의 클로저 작동 방식 때문에 useEffect 내부에서 참조하는 값이 해당 값의 최신 상태가 아닌, 훅이 처음 실행될 당시의 값을 참조하는 상황을 의미한다.
함수가 생성될 때 그 함수가 참조하는 외부 변수들을 함수와 함께 기억한다. 즉, 함수 내에서 외부 변수를 참조하는 경우 함수가 생성된 시점의 환경을 기억하는 것!
클로저에서 외부 변수들을 참조하는 것을 캡쳐라고 부른다. 함수가 특정 값을 참조할 때 그 값을 캡쳐하여 나중에 함수가 실행될 때 참조할 수 있도록 하는것!
내 코드에서는 useEffect가 실행되는 시점에 따라 상태 값이 예상대로 갱신되지 않고 그로 인해 reset() 함수가 호출되어 폼 값이 초기화되는 타이밍이 맞지 않는 상황!!
reset()이 호출될 때, defaultValues로 설정된 값들이 초기화되기 전에 reset이 먼저 실행되어 폼 값이 빈값으로 초기화되고, 그 이후에 useEffect에서 외부 데이터를 가져오는 순서 문제로 인해 값이 갱신되는 타이밍이 맞지 않아 발생한 문제였다.
결론
useEffect내부에서 정의한 것은 렌더링과 동시에 실행되는것이 아니라
실행 예약 개념이라는것이다.
렌더링이 완료된 후 브라우저가 화면을 그린 후에 작업을 예약하고, 그 작업은React가 리렌더링을 완료한 이후에 실행한다.
컴포넌트가 렌더링되고 DOM 업데이트 -> useEffect는 실행할 작업을 비동기적으로 예약
-> react가 리렌더링 실행 -> useEffect는 그 변경이 화면에 반영된 이후에 실행
useEffect는 실제로 비동기적으로 실행되기 때문에 렌더링이 완료된 후 다음 타이밍에 실행되는 방식이다. 정확히 말하면 setter가 실행 예약되고, 리렌더링이 이루어지기 전에 reset()이 호출되면서 발생한것이다.
그럼 우선 해결할 수 있는 방법을 고민해보자
브라우저가 화면을 그리기 전에 setter가 업데이트 되고 reset이 실행되어야 한다는 부분에서 useEffect가 아닌 useLayoutEffect에 대해 알아보았다.
| useEffect | useLayoutEffect | |
|---|---|---|
| 실행 시점 | 렌더링 후 브라우저가 화면을 그린 뒤 | 렌더링 후 브라우저가 화면을 그리기 전 |
| 실행 방식 | 비동기적 (브라우저 화면 그리기 후) | 동기적 (브라우저 화면 그리기 전) |
| 주요 용도 | 비동기 작업, 데이터 가져오기, API 호출, 이벤트 리스너 등록 등 | UI 레이아웃 측정, DOM 요소 크기 및 위치 조정, 화면 그리기 전에 UI 수정 등 |
| 렌더링 성능 | 렌더링 성능에 영향을 미치지 않음 | 크고 복잡한 작업일 경우 화면 그리기 전에 동기적으로 실행되므로 성능에 영향을 미칠 수 있음 |
문서로만 보았을 때는 useEffect를 useLayoutEffect로 변경하면 해결 될 것이라 보인다. 하지만 렌더링 성능에서 문제가 생길 수 있지만 회원가입 플로우의 간단한 부분이라 큰 영향을 미칠 것 같지 않다는 판단에 useLayoutEffect로 코드를 수정해보았다
useLayoutEffect(() => {
console.log('useEffect1');
if (searchParams.get('success')) {
console.log('useEffect > success');
setIsActive(true);
setSignupData({ name: storageName, phone: storagePhone });
reset();
}
console.log('useEffect2');
}, []);
마찬가지로 실행 여부를 판단하기 위해 console.log또한 그대로 유지 한 후 테스트를 해보았다!

본인인증을 한 후 리렌더링이 이루어진 이후에도 SessionStorage data가 잘 적용되며 휘발되지 않는것을 확인했다! 문제 해결 완료!!
기존에도 SessionStorage data가 기본값으로 잘 적용되었지만 useEffect의 실행순서에서 꼬여버린것이라 비교적 금방 문제 해결을 할 수 있었던 것 같다
useEffect만 사용해봤고 useLayoutEffect는 언제 사용하는지 몰랐는데 이것때문에 이틀 내내 고민했다.. 나름 다양한 해결책을 찾아보려 노력했었던 것 같다.
클로저에 대해 항상 어려워만 했고 이론만 공부하기에는 이 개념은 어렵다 생각했는데 막상 내 코드에서 이런 문제를 현실에서 마주하니 오히려 이해못했던 이론이 깔끔하게 정리된 느낌이었다.
이제 useLayoutEffect의 역할과 장단점, useEffect와의 다른점을 배우고 사용해보았으니 앞으로 이런 로직을 구성할 때 더 다양한 방법으로 고안 할 수 있을 것 같고 문제를 다시금 마주했을때는 빠르게 해결 할 수 있을 것 같다!
오늘도 조금 더 배웠다!
덕분에 깜빡임 해결했습니다! 감사해요