[React] useState 사용 후 state가 undefined로 나올 때

박기영·2022년 7월 24일
0

React

목록 보기
1/32
post-custom-banner

문제 상황

useState를 사용해서 state를 업데이트 할 때 분명히 코드 상으로는 업데이트가 되야지 정상인데, undefined가 나올 때가 있다.
console.log()를 찍어보면 업데이트가 된게 맞는데, 왜 state는 변화가 없을까?

아래 코드를 살펴보자.(typescript를 사용했지만, 타입 설정 외에 JS와 다른 건 없다)
필자가 토이 프로젝트에서 겪은 상황 그대로 가져와봤다.
구현하고자 했던 것은 drop1을 ref로 가지는 컴포넌트의 화면상 left 좌표 값을 얻어와서, 그 값을 dropWidth라는 state에 업데이트하는 것이었다.
또한, 이 업데이트는 useEffect를 통해서 resize와 mouseover 이벤트가 발생할 때만 동작할 것이다.
그런데...결과는 아래와 같았다.(예시 상황)

const drop1 = useRef<HTMLLIElement>(null);
// 초기 state 설정이 비어 있음
const [dropWidth, setDropWidth] = useState<number | undefined>();

const checkDropWidth = () => {
  let num = drop1.current?.getBoundingClientRect().left;

  console.log("num", num); // 283

  setDropWidth(num);

  console.log(dropWidth);  // undefined
  };

useEffect(() => {
  window.addEventListener("resize", checkDropWidth);
  drop1.current?.addEventListener("mouseover", checkDropWidth);

  return () => {
    window.removeEventListener("resize", checkDropWidth);
    drop1.current?.removeEventListener("mouseover", checkDropWidth);
  };
}, []);

코드만 보면 왜 안되는지 솔직히 이해가 안된다.(초심자 입장에서)
당연히 283이 dropWidth에 들어갈 것이라고 생각했는데, 뭐가 문제일까?

이유

setState는 queue-based system에서 비동기(asynchronous)적으로 동작한다.
비동기적으로 동작하기 때문에 num 변수가 업데이트 되기 전에 setState가 되버린 것이다.
num 변수가 할당되기 전에 값을 setState해버리면, useState 초기 값에 아무 것도 넣지 않았으므로 undefined가 나오는 것이다.
따라서, 우선적으로 해야할 일은 useState의 초기값을 설정해주는 것이다.
사실 이 방법은 위 문제의 근본적인 해결법이 아니라고 생각한다.
다만 초기값 설정을 해두는 것은 어떤 곳에서든 dropWidth를 console.log()로 찍어볼 때 undefined는 피할 수 있게 해줄 것이다.
그 다음 해야할 일은 useEffect에서 dependency 설정을 해주는 것이다.
setState의 대상인 state로 설정해주면 된다. 위 예시에서는 dropWidth가 그 대상이 되겠다.

적용

이유를 알았으니 코드에 적용해보자.

  const drop1 = useRef<HTMLLIElement>(null);
  const [dropWidth, setDropWidth] = useState<number | undefined>(0);

  const checkDropWidth = () => {
    let num = drop1.current?.getBoundingClientRect().left;

    console.log("num", num);  // 283

    setDropWidth(num);

    console.log(dropWidth);  // 0(초기값) -> 283(이벤트 발생 시)
  };

  useEffect(() => {
    window.addEventListener("resize", checkDropWidth);
    drop1.current?.addEventListener("mouseover", checkDropWidth);

    return () => {
      window.removeEventListener("resize", checkDropWidth);
      drop1.current?.removeEventListener("mouseover", checkDropWidth);
    };
  }, [dropWidth]);

useState에 초기값도 넣어줬고, useEffect에 dependency도 설정해줬다.
이제 코드가 의도한대로 작동한다!!

결론

  1. useState 초기값 설정하기.
  2. useEffect의 dependency를 state로 설정하기.

참고 자료

참고 자료1
참고 자료2
참고 자료3
참고 자료4
참고 자료5

profile
나를 믿는 사람들을, 실망시키지 않도록
post-custom-banner

0개의 댓글