[React.js] setTimeout 사용 시 최신 상태(state)가 반영되지 않는 이슈 처리

2025. 4. 7. 18:03·개발 일상

서론

React에서 타이머(setTimeout)를 이용하여 일정 시간 후에 특정 함수를 호출하는 기능을 구현하던 중,
함수 내부에서 참조하는 상태(state)가 최신값이 아닌 과거 값으로 작동하는 문제가 발생했다. 문제 원인을 체크해보니 React의 비동기 렌더링 이슈가 아닌 자바스크립트의 클로저(Closure)의 특징으로 발생한 것이라는 걸 알게 되었다.

문제 상황

const goToMaintainPage = () => {
  // 작업 상태값을 저장하고 다음 페이지로 이동
// mySwrState : 생성한 커스텀 useSWR 임
  setMySwrState({
    ...mySwrState,
    director: directorName, // useState 값(생성했다고 가정)
    workerNumber: member, // useState 값
    // ...
  });

  localStorage.setItem('director', directorName);
  navigate('/maintainPage');
};

const resetInactivityTimer = () => {
  if (inactivityTimerRef.current) clearTimeout(inactivityTimerRef.current);
  inactivityTimerRef.current = setTimeout(goToMaintainPage, 10000); // 10초 후 자동 이동
};

위 코드는 10초 동안 사용자 입력이 없으면 goToMaintainPage()를 호출하도록 설정한 코드이다.

문제는, setTimeout()이 호출되었을 때 당시의(그 시점의) goToMaintainPage 함수가 복사된 상태로 기억된다는 점이다.

즉, goToMaintainPage() 내부에서 참조하는 directorName, member 등은
타이머가 설정된 당시의 값이 된다.

원인 분석

자바스크립트의 함수 클로저는 정의 당시의 외부 변수 상태를 캡처한다.
setTimeout(goToMaintainPage, 10000)이 실행되면, 10초 뒤 호출될 함수는
당시의 goToMaintainPage 함수이며, 이 함수는 당시의 상태값들을 참조한다.

React에서 useState, useSWR로 관리하는 값들도 그 대상이다.
setTimeout은 함수 자체를 복사해 캡처하는 것이지, 내부 값을 나중에 갱신해서 반영하는 구조가 아니다.

따라서 화면에서 값이 바뀌었어도, 10초 후 실행될 goToMaintainPage
변경 전의 오래된 값(setTimeout 으로 호출되었을 때 당시의 값)을 사용하게 되는 것이다.

해결 방법: useRef를 활용한 상태 보존


const accessInfoRef = useRef({
  director: directorName,
  workerNumber: member,
  // 기타 필요한 값들
});

// 상태값이 변경될 때마다 최신값을 ref에 동기화
useEffect(() => {
  accessInfoRef.current.director = directorName;
}, [directorName]);

useEffect(() => {
  accessInfoRef.current.workerNumber = member;
}, [member]);

const goToMaintainPage = () => {
  setMySwrState({
    ...mySwrState,
    director: accessInfoRef.current.director,
    workerNumber: accessInfoRef.current.workerNumber,
  });

  localStorage.setItem('director', accessInfoRef.current.director);
  navigate('/maintainPage');
};

이렇게 하면 goToMaintainPage는 useRef의 .current 값을 참조하므로
항상 최신 상태 값을 보장하게 된다.

정리

  • setTimeout(fn, time)은 당시의 fn을 복사하여 나중에 실행한다.
  • 이때 함수 내부에서 사용하는 변수(state)는 최신값이 아닌 캡처된 클로저 값이다.
  • 이를 방지하려면 useRef에 최신 상태를 저장해두고, 타이머가 실행될 때는 .current 값을 사용해야 한다.

정리하자면 이 이슈는 자바스크립트 언어 차원에서 클로저가 동작하는 방식에서 비롯된 이슈이다.

타이머, 이벤트 리스너, setInterval, addEventListener 등
시간이 지난 후 실행되는 함수에는 항상 이 클로저 문제가 따라붙을 수 있다.

그러므로, 시간차 호출 + 상태값 활용이 필요한 상황에서는
useRef를 통해 최신값을 보존하고 사용하는 패턴을 기억하는 것이 좋을 듯 하다.

저작자표시 (새창열림)

'개발 일상' 카테고리의 다른 글

대량의 파일, 효율적으로 압축 & 전송하기 – 우분투 기반 백업 최적화  (2) 2025.06.02
[python] data.dict() vs jsonable_encoder(). 그리고 datetime 쿼리 삽질기  (0) 2025.05.26
MongoDB Aggregation을 활용한 통계 API 구현(python)  (1) 2025.05.22
주기적인 MongoDB 서비스 Down 증상 해결  (0) 2025.05.19
Ubuntu의 crontab에서 GUI 프로그램 실행 스케쥴 등록하기(ubuntu 22.04 기준)  (0) 2022.11.04
'개발 일상' 카테고리의 다른 글
  • [python] data.dict() vs jsonable_encoder(). 그리고 datetime 쿼리 삽질기
  • MongoDB Aggregation을 활용한 통계 API 구현(python)
  • 주기적인 MongoDB 서비스 Down 증상 해결
  • Ubuntu의 crontab에서 GUI 프로그램 실행 스케쥴 등록하기(ubuntu 22.04 기준)
레실이
레실이
  • 레실이
    레실이의 티스토리
    레실이
  • 전체
    오늘
    어제
    • 분류 전체보기 (91)
      • SE Bootcamp 내용 정리 (63)
      • 알고리즘 연습 (7)
      • Project 주저리 (4)
      • 기술 면접 source (3)
      • 개발 일상 (12)
      • 생성 AI 활용 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    비동기
    Python
    CSR
    IT
    PickAndDrink
    데이터베이스
    promise
    state
    자료구조
    DOM
    인증/보안
    문자열
    Linux
    객체
    JS
    node.js
    CSS
    useState
    MVC
    ORM
    CORS
    node
    JavaScript
    react
    mongoDB
    Ajax
    알고리즘
    useRef
    fastapi
    ubuntu
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
레실이
[React.js] setTimeout 사용 시 최신 상태(state)가 반영되지 않는 이슈 처리
상단으로

티스토리툴바