HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
🐣
프론트엔드 데브코스 4기 교육생
/
🍎
성기동팀
/
🪄
NIRVANA MANAGER
/
posting/NewPost
posting/NewPost
posting/NewPost

posting/NewPost

종류
컴포넌트
작성자

내가 헷갈려서 적는 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.