React 18의 useTransition으로 검색 UX 개선하기
문제 상황
상품 검색 페이지에서 사용자가 입력할 때마다 5000개 이상의 아이템을 필터링하는 로직이 있었다. 타이핑할 때마다 버벅거리는 현상이 발생했고, 특히 저사양 디바이스에서 심각했다.
기존에는 debounce로 해결하려 했지만, 입력 반응이 늦어져 오히려 UX가 나빠졌다.
useTransition 도입
React 18의 useTransition을 사용해 입력과 필터링 렌더링의 우선순위를 분리했다.
function SearchPage() {
const [query, setQuery] = useState('');
const [filteredItems, setFilteredItems] = useState(items);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
setQuery(value); // 즉시 반영
startTransition(() => {
// 낮은 우선순위로 처리
const filtered = items.filter(item =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setFilteredItems(filtered);
});
};
return (
<>
<input value={query} onChange={handleChange} />
{isPending && <Spinner />}
<ItemList items={filteredItems} />
</>
);
}
결과
입력 필드는 즉시 반응하고, 리스트 렌더링은 브라우저가 여유 있을 때 처리된다. 사용자 입력이 절대 막히지 않으면서도 debounce처럼 응답이 지연되는 느낌이 없다.
isPending 플래그로 로딩 상태를 표시해 사용자에게 처리 중임을 알릴 수 있었다.
주의사항
startTransition 내부의 상태 업데이트만 낮은 우선순위로 처리된다. 외부 API 호출이나 side effect는 여전히 별도 처리가 필요하다. 단순히 렌더링 비용이 큰 경우에 효과적이었다.