React 18의 useTransition으로 검색 필터 성능 개선하기

문제 상황

5000개 이상의 row를 가진 테이블에서 검색 필터를 구현했는데, 입력할 때마다 UI가 버벅이는 문제가 발생했다. 사용자가 타이핑할 때마다 전체 테이블을 리렌더링하면서 입력 자체가 지연되는 현상이었다.

기존에는 debounce로 처리했지만, 여전히 입력 반응성이 떨어졌다.

useTransition 적용

React 18의 useTransition을 사용해 입력 업데이트(긴급)와 필터링 업데이트(전환)를 분리했다.

import { useState, useTransition } from 'react';

function DataTable({ data }) {
  const [filterText, setFilterText] = useState('');
  const [deferredFilter, setDeferredFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setFilterText(e.target.value);
    startTransition(() => {
      setDeferredFilter(e.target.value);
    });
  };

  const filtered = data.filter(item => 
    item.name.includes(deferredFilter)
  );

  return (
    <div>
      <input 
        value={filterText} 
        onChange={handleChange}
        placeholder="검색..."
      />
      {isPending && <span>검색 중...</span>}
      <table>
        {filtered.map(item => (
          <tr key={item.id}>
            <td>{item.name}</td>
          </tr>
        ))}
      </table>
    </div>
  );
}

결과

입력 필드는 즉시 반응하고, 무거운 필터링 작업은 백그라운드에서 처리된다. isPending 상태로 로딩 표시도 자연스럽게 추가할 수 있었다.

debounce와 달리 인위적인 지연 없이 React의 우선순위 시스템을 활용할 수 있어서 UX가 더 자연스러웠다. 프로덕션 배포 후 사용자 피드백도 긍정적이었다.

Concurrent 기능을 실무에 처음 적용해봤는데, 사용자 입력이 많은 화면에서 유용하게 쓸 수 있을 것 같다.