- Published on
[회고록][트러블슈팅] Stale Closure
- Authors
- Name
- Jihwan Seong
배경
- react에서 예상치 않게 상태가 동작하는 상황이 발생
- 상태가 예상치 않게 동작한 원인을 파악하고 해결법을 찾아보는 시간을 갖는다.
트러블 슈팅
Stale Closure 이슈
상황
- 컴포넌트 함수에서 정의한 object state를 클로저 함수에서 사용.
- 클로저 함수가 호출되면, object state를 사용 한뒤, 갱신한다.
문제
- 클로저에 의해 object state를 갱신하지만, 이후에 다시 호출된 클로저 함수는 갱신된 state를 사용하지 않고 갱신전 state를 계속해서 사용함.
const [findResult , setFindResult] = useState<FindPaintingResult>(props.findResult);
const searchParam = useSearchParams();
const loadMorePainting = async () => {
if(!findResult.isMore || isLoadingRef.current){
console.log(`return [loadMorePainting]`);
return;
}
isLoadingRef.current = true;
...
const result : FindPaintingResult = await findPainting(searchTitle,searchArtist,searchTags,searchStyles,findResult.pagination+1);
console.log(`update setFindResult, page=${findResult.pagination}, update to ${result.pagination}`);
setFindResult(result);
setSearchPaintings(prev=> [...prev, ...result.data]);
console.log(`setIsLoading : false. previous : ${isLoadingRef.current}`);
isLoadingRef.current = false;
}
// 스크롤 이벤트 핸들러
useEffect(() => {
const handleScroll = () => {
console.log(`call handleScroll`);
if (
window.innerHeight + window.scrollY >= document.body.offsetHeight - 500 &&
!isLoadingRef.current
) {
console.log(`call loadMorePainting`);
loadMorePainting();
}
};
console.log('[useEffect] : for config scroll event');
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [findResult]);
해결방법
1. useState 대신 useRef 사용
- 직접적으로 JSX 컴포넌트 렌더링에 필요치 않은 데이터이므로, useRef를 사용한다.
- 특징
- 클로저 함수가 ref를 캡처하므로, 데이터가 갱신되도 클로저는 동일한 ref를 통해 갱신된 값을 사용 가능
- state일 때보다 즉각적으로 값이 반영됨
- 리렌더링을 발생시키지 않음
2. useEffect dependency 활용
- useEffect를 활용하여 상태가 변경될 때 마다, 클로저 함수가 변경된 상태를 다시 캡처하도록 한다.
- 특징
- .. 솔직히 무슨 장점이 있는지는 모르겠음...
3. setState 함수에 콜백 함수 넘기기
- setState 함수에 콜백을 넘기면, 콜백함수의 인자는 항상 최신 상태를 참조하게 됨
- 현재 로직은 상태 변경시 이전상태에 의존하므로, 항상 최신 상태를 참조하게되면 문제가 해결됨.
선택
- 1번 방법을 선택하였다.
- 스크롤링 다운 이벤트는 매우빠르게 여러번 발생하므로, 즉각적으로 데이터가 변경될 필요가 있다.
- 2번 방법 적용시, 상태(데이터)가 변경되지 않았지만 isLoadingRef는 즉각적으로 변경되어 loadMorePainting() 함수가 호출된다.
Ref
- Stale-Closure 자세한 설명 및 간단 에제 https://stackoverflow.com/questions/62806541/how-to-solve-the-react-hook-closure-issue
- Stale-Closure에 대한 여러가지 경우( JS, React)와 그에대한 해결책 https://dmitripavlutin.com/react-hooks-stale-closures/