React 19 Concurrent 훅 - useTransition, useOptimistic vs React Query
useTransition
const [isPending, startTransition] = useTransition()
function handleFilter(value: string) {
startTransition(async () => {
const data = await fetchData(value)
startTransition(() => setResults(data)) // await 뒤 set 은 다시 감싼다
})
}
startTransition으로 감싼 state 업데이트는 **비긴급(non-urgent)**으로 처리- 급한 업데이트(타이핑, 클릭 피드백)를 먼저 처리하고, transition 작업은 뒤로 미룸
isPending으로 로딩 상태 확인, 기존 UI 유지하면서 백그라운드에서 새 UI 준비await뒤의 set 은 transition 으로 안 잡힌다 — 한 번 더startTransition으로 감싼다1
useOptimistic
const [optimisticItems, addOptimistic] = useOptimistic(
items,
(current, newItem) => [...current, newItem]
)
function handleAdd(item: Item) {
startTransition(async () => {
addOptimistic(item) // 즉시 UI 반영
await saveToServer(item) // Action 이 끝나면 items 로 돌아간다
})
}
- 서버 응답 전에 UI 먼저 업데이트, 실패 시 자동 복구 — 낙관적 값은 Action 이 진행 중일 때만 보이고 끝나면
items가 보인다. 그래서 성공하면items를 갱신해야 남는다2 - setter 는 Action(
startTransition·formaction) 안에서 불러야 한다. 밖에서 부르면 경고와 함께 바로 되돌아간다3 - 좋아요 버튼, 장바구니 추가 같은 인터랙션에 적합
현실: 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()
})
}}>
Footnotes
-
useTransition – React: “Any async calls that are awaited in the action will be included in the Transition, but currently require wrapping any set functions after the await in an additional startTransition …” ↩
-
useOptimistic – React: “Optimistic state only renders while an Action is in progress, otherwise value is rendered.” ↩
-
useOptimistic – React: “When you call the setter outside an Action, the optimistic state will briefly appear and then immediately revert back to the original value.” ↩