내가 헷갈려서 적는 NewPost 동작
헷갈리는 이유! 훅 커스텀이 두개나 있는데 이 훅 커스텀 두개가 각각 연관돼 있기 때문이다.
useSessionStorage
sessionStorage 에 저장된 값과, 저장할 수 있는 함수를 반환한다.
useDebounce
원하는 시각과, 디바운싱할 함수를 전달받는다. 타이머 클리어 함수만 반환한다.
const [posting, setPosting] = useState(''); const [prevPosting, savePosting] = useSessionStorage('posting', { posting, ...meditationInfo }); const clear = useDebounce( 200, () => { savePosting({ posting, ...meditationInfo }); }, [posting] );
useSessionStorage
savePosting
은 사용자가 글을 쓴 내용과 meditationPage
로 부터 받은 명상 정보를 세션 스토리지에 저장한다.// useSessionStorage const [value, setValue] = useState(); useEffect(() => { sessionStorage.setItem(key, JSON.stringify(value)); }, [key, value]) return [value, setValue];
이 때 반환하는 값 중
setValue
가 NewPost 에선 savePost
의 역할을 한다. 사용자가 포스트를 쓰면 포스트 상태의 값이 바뀜으로써 포스트가 저장된다.// NewPost useEffect(() => { savePost(posting); }, [posting])
이를 디바운싱으로 처리하고 싶어
useDebounce
훅을 추가했다.useDebouncing
useDebouncing 은 디바운싱 할 함수를 받아 디바운스 시간마다 처리한다.
const timer = useRef(null); const callback = useRef(fn); const run = useCallback(() => { timer.current && clearTimeout(timer.current); timer.current = setTimeout(() => { callback.current(); }, time) }, [time]); const clear = useCallback(() => { timer.current = clearTimeout(timer.current); }) // dependency 의 값이 바뀔 때 마다 디바운싱 진행 useEffect(() => { run() }, [dependency]);
그리고 우리는 save 작업을 디바운싱으로 처리해주고 싶으므로 savePost 를 debounce 값으로 전달해주면 된다.
// NewPost useDebounce(200, () => {savePosting({posting, ...meditationInfo})}, [posting]);
문제
posting 이 바뀔 때 마다 디바운싱도 잘 실행되고, callback 도 잘 실행되는 걸 확인했다.
const run = useCallback(() => { timer.current && clearTimeout(timer.current); timer.current = setTimeout(() => { console.log('디바운스 실행!!'); callback.current(); }, time) }, [time]);
const clear = useDebounce( 200, () => { console.log('콜백 실행!!'); savePosting({ posting, ...meditationInfo }); }, [posting] );
그런데 문제는 savePosting 이 실행이 되지 않는 건지 계속 sessionStorage 에 빈 값이 돌았다.
왜 savePosting 이 안되는 걸까…! 그러다 해당 코드를 useDebounce 에 추가하니 정상적으로 작동이 되었다.
useEffect(() => { callback.current = fn; }, [fn]); // ...useDebounce 기존 코드 useEffect(() => clear, [clear]);
특히 아래 clear 를 해주지 않으면 다음과 같은 에러가 발생했다.
Rendered fewer hooks than expected. This may be caused by an accidental early return statement.
함수가 변경되지 않는 문제
콜백 함수를 넘겨줄때 다음과 같이 익명함수의 형태로 넘겨주게 된다.
const clear = useDebounce( 200, () => { savePosting({ posting, ...meditationInfo }); }, [posting] );
그리고 이렇게 넘겨준 함수는 다음과 같이 useRef 를 사용해 저장한다.
const callback = useRef(fn);
이렇게 되면 posting 이 될 때마다 useDebounce 로 새로운 콜백함수를 넘겨주게 된다. 같은 함수이지만 매번 메모리 할당을 다시 하고, 새로운 매개변수로 취급되는 것이다.
하지만 callback 변수는 바뀐 함수를 인지할 수 없으므로 바뀐 fn 을 가지지 못한다. 바뀐 fn 을 가지지 못하면 useDebounce 를 처음 선언할 당시의 콜백만을 저장한 상태이다. 그리고 해당 시점의 posting 은 빈 문자열의 값이므로 sessionStrage 에 저장이 되지 않는 문제가 생기는 것이다.
useEffect(() => { callback.current = fn; }, [fn]);
따라서 위와 같이 fn 이 바뀔 떄마다 새로 callback 을 바꿔주는 방법으로 posting 에 대한 정보도 업그레이드 한다.
clear 해주지 않으면 어떻게 될까?
useEffect 로 clear 를 해주지 않으면 에러가 나게 된다.
useEffect(() => clear, [clear]);
해당 함수가 없다면 컴포넌트가 언마운트 될 때 이전 타이머를 취소시키지 못하는 문제가 생긴다. 위 함수는 두가지 경우에 실행된다.
- clear 함수가 바뀌는 경우
- 컴포넌트가 언마운트 되는 경우
이렇게 clear 함수로 컴포넌트가 언마운트 될 때 이전 타이머를 취소시키지 않으면 다른 컴포넌트로 이동해도 타이머가 계속 실행되어 메모리 누수등의 문제가 발생할 수 있다.
🥲 그런데 아래의 에러는 리액트 훅과 관련된 에러인 것 같은데… 왜 발생한 건지 이해를 못하겠다!
Rendered fewer hooks than expected. This may be caused by an accidental early return statement.