React 18 useTransition으로 검색 UX 개선하기

문제 상황

상품 관리 페이지에 5000개 이상의 아이템을 다루는 검색 기능이 있었다. 사용자가 검색어를 입력할 때마다 input이 버벅이는 문제가 발생했다. 디바운스를 적용했지만 입력 자체가 느려지는 건 해결되지 않았다.

useTransition 적용

React 18에서 정식 출시된 useTransition을 사용해 상태 업데이트 우선순위를 분리했다.

import { useState, useTransition } from 'react';

function ProductSearch({ products }) {
  const [query, setQuery] = useState('');
  const [filteredProducts, setFilteredProducts] = useState(products);
  const [isPending, startTransition] = useTransition();

  const handleSearch = (e) => {
    const value = e.target.value;
    setQuery(value); // 즉시 업데이트
    
    startTransition(() => {
      // 무거운 필터링은 낮은 우선순위로
      const filtered = products.filter(p => 
        p.name.toLowerCase().includes(value.toLowerCase())
      );
      setFilteredProducts(filtered);
    });
  };

  return (
    <>
      <input 
        value={query} 
        onChange={handleSearch}
        placeholder="상품 검색..."
      />
      {isPending && <span>검색 중...</span>}
      <ProductList items={filteredProducts} />
    </>
  );
}

결과

입력 필드는 즉각 반응하고, 리스트 렌더링만 지연되어 타이핑 경험이 크게 개선되었다. isPending 상태로 로딩 표시도 자연스럽게 추가할 수 있었다.

기존에는 디바운스로 300ms 지연을 뒀는데, 이제는 입력은 즉시 반영되고 React가 알아서 렌더링 우선순위를 조정해준다. Concurrent 기능의 실용성을 체감했다.

주의사항

startTransition 내부의 setState는 동기적으로 호출되어야 한다. setTimeout이나 비동기 함수 내부에서는 동작하지 않는다.