React 19 Concurrent 훅 - useTransition, useOptimistic vs React Query
useTransition
const [isPending, startTransition] = useTransition()
function handleFilter(value: string) {
startTransition(async () => {
const data = await fetchData(value)
setResults(data)
})
}
startTransition으로 감싼 state 업데이트는 **비긴급(non-urgent)**으로 처리- 급한 업데이트(타이핑, 클릭 피드백)를 먼저 처리하고, transition 작업은 뒤로 미룸
isPending으로 로딩 상태 확인, 기존 UI 유지하면서 백그라운드에서 새 UI 준비
useOptimistic
const [optimisticItems, addOptimistic] = useOptimistic(
items,
(current, newItem) => [...current, newItem]
)
async function handleAdd(item: Item) {
addOptimistic(item) // 즉시 UI 반영
await saveToServer(item) // 실패하면 자동 rollback
}
- 서버 응답 전에 UI 먼저 업데이트, 실패 시 자동 복구
- 좋아요 버튼, 장바구니 추가 같은 인터랙션에 적합
현실: Query가 이미 너무 편함
const { data, isPending } = useQuery({
queryKey: ['items', filter],
queryFn: () => fetchItems(filter),
})
useMutation({
mutationFn: addItem,
onMutate: async (newItem) => {
const previous = queryClient.getQueryData(['items'])
queryClient.setQueryData(['items'], (old) => [...old, newItem])
return { previous }
},
onError: (err, _, context) => {
queryClient.setQueryData(['items'], context.previous)
},
})
캐싱, 리페치, devtools, stale-while-revalidate까지 한 방에 해결. 팀에서 이미 쓰고 있으면 “굳이?” 됨.
useTransition이 의미 있는 지점 - 무거운 클라이언트 연산, Next.js Server Actions 조합
<form action={(formData) => {
startTransition(async () => {
await createItem(formData)
router.refresh()
})
}}>