Skip to content

useEffectEvent for Stable Callback Refs

useEffect가 불필요하게 다시 실행되는 것을 방지하면서도, 함수 내부에서는 항상 최신 값을 참조할 수 있도록 만들기

Incorrect (effect re-runs on every callback change)

tsx
/*
 * 부모 컴포넌트가 리렌더링될 때마다 onSearch 함수가 새로 생성되면,
 * 실제 검색어(query)가 바뀌지 않았는데도 타이머가 계속 초기화된다는 점
 */
function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState('');

  /**
   * 문제: onSearch가 의존성 배열에 포함되어 있음
   * 부모 컴포넌트가 리렌더링되어 onSearch 함수의 참조값이 바뀌면,
   * 사용자가 입력을 멈췄음에도 타이머가 불필요하게 초기화 됨
   */
  useEffect(() => {
    const timeout = setTimeout(() => onSearch(query), 300);
    return () => clearTimeout(timeout);
  }, [query, onSearch]);
}

Correct (using React's useEffectEvent):

tsx
import { useEffectEvent } from 'react';

function SearchInput({ onSearch }: { onSearch: (q: string) => void }) {
  const [query, setQuery] = useState('');
  /**
   * onSearch를 이벤트 함수로 캡슐화
   * 이 함수는 항상 최신 props인 onSearch를 바라보지만,
   * 함수 자체의 정체성은 절대 변하지 않는 안정적인 상태
   */
  const onSearchEvent = useEffectEvent(onSearch);

  useEffect(() => {
    // query가 바뀔때만 진행하며, 실행 시점에는 최신 onSearch 호출
    const timeout = setTimeout(() => onSearchEvent(query), 300);
    return () => clearTimeout(timeout);
  }, [query]);
}