본문 바로가기
개발일지-에러핸들링

React useEffect로 이벤트 핸들러를 부착했을 경우 상태값을 제대로 읽어오지 못하는 문제

by improvise0828 2021. 12. 14.

 

문제가 된 코드

const FollowPointer = () => {
  const [cursorX, setCursorX] = useState(null);
  const [cursorY, setCursorY] = useState(null);

  // 마우스 커서 위치 기록
  const mouseMoveHandler = (e) => {
    console.log(cursorX, cursorY);
    setCursorX(e.clientX);
    setCursorY(e.clientY);
  };

  // 마우스 커서 위치 기록
  useEffect(() => {
    window.addEventListener("mousemove", mouseMoveHandler);
    return () => window.removeEventListener("mousemove", mouseMoveHandler);
  }, []);

  return (
    <div>
      {cursorX} {cursorY}
    </div>
  );
};

export default FollowPointer;

 

문제

console.log(cursorX, cursorY)가 제대로 된 마우스의 좌표를 출력하지 못함

 

문제 원인

React의 라이프 사이클에 대한 이해

컴포넌트가 업데이트되면서 이벤트 핸들러가 재생성되면서 참조값이 변하고

useEffect로 부착한 이벤트 핸들러는 새로 생성된 이벤트 핸들러로 업데이트되지 못해서 발생한 문제

 

순서대로 과정을 정리하면

1. 컴포넌트가 마운트됨

2. mouseMoveHandler 이벤트 리스너 생성

3. mouseMoveHandler 이벤트 리스너가 useEffect를 사용해서 window 객체에 부착됨

4. 마우스가 움직임 -> mouseMoveHandler 이벤트 리스너는 cursorX, cursorY의 상태값을 업데이트

5. 상태가 업데이트되었으므로 컴포넌트 업데이트

6. 컴포넌트가 업데이트되면서  mouseMoveHandler 이벤트 리스너가 재생성

7. 기존의 mouseMoveHandler 이벤트 리스너는 window 객체에 부착되어 있는 상태로 남아있고

새로 선언된 mouseMoveHandler 이벤트 리스너는 window 객체에 부착되지 않음

8. 상태값이 변하면서 상태값의 참조주소도 변했으나 기존의 mouseMoveHandler 이벤트 리스너도

업데이트된 상태값의 주소를 알지 못하므로 변경된 상태값을 출력하지 못하고 초기에 읽어들인 상태값만을 계속해서 출력함

 
수정된 코드
import { useCallback, useState, useEffect } from "react";

const FollowPointer = () => {
  const [cursorX, setCursorX] = useState(null);
  const [cursorY, setCursorY] = useState(null);

  // 마우스 커서 위치 기록
  const mouseMoveHandler = useCallback(
    (e) => {
      console.log(cursorX, cursorY);
      setCursorX(e.clientX);
      setCursorY(e.clientY);
    },
    [cursorX, cursorY]
  );

  // 마우스 커서 위치 기록
  useEffect(() => {
    window.addEventListener("mousemove", mouseMoveHandler);
    return () => window.removeEventListener("mousemove", mouseMoveHandler);
  }, [mouseMoveHandler]);

  return (
    <div>
      {cursorX} {cursorY}
    </div>
  );
};

export default FollowPointer;