/////

React Concurrent UI Contracts: startTransition, useTransition, useDeferredValue, and Interruption-Safe Pending-State Failure Modes

React concurrent UI의 핵심 계약은 “무엇을 긴급 업데이트로 남기고, 무엇을 interruptible background work로 낮출 것인가”이다. startTransition / useTransition은 특정 state update를 Transition으로 표시해 입력 같은 urgent update가 막히지 않도록 한다. useDeferredValue는 값 자체를 지연시켜 하위 UI가 이전 값을 잠시 보여 주면서 새 값 렌더링을 backgr

/////

Summary#

React concurrent UI의 핵심 계약은 “무엇을 긴급 업데이트로 남기고, 무엇을 interruptible background work로 낮출 것인가”이다. startTransition / useTransition은 특정 state update를 Transition으로 표시해 입력 같은 urgent update가 막히지 않도록 한다. useDeferredValue는 값 자체를 지연시켜 하위 UI가 이전 값을 잠시 보여 주면서 새 값 렌더링을 background에서 시도하게 한다.

실패 모드는 주로 async 경계, pending-state 오해, Suspense fallback 처리, race condition에서 발생한다. 특히 await 이후의 state update는 자동으로 같은 Transition에 포함되지 않을 수 있어 다시 startTransition으로 감싸야 한다. useDeferredValue는 debounce나 request cancellation이 아니므로 stale UI와 stale network result를 구분해서 다뤄야 한다.

Key Points#

  • startTransition(scope)scope 안에서 동기적으로 실행되는 state update를 Transition으로 표시한다.
  • Transition update는 non-blocking이며 더 높은 우선순위의 입력 업데이트에 의해 interrupt될 수 있다.
  • React 공식 문서는 Transition update가 text input 제어에는 사용할 수 없다고 명시한다.

  • useTransition()[isPending, startTransition]을 제공한다.

  • isPending은 Transition 작업이 아직 완료되지 않았음을 UI에 표시하는 데 쓴다.
  • 단, async 작업을 포함할 때는 경계를 주의해야 한다. await 이후 발생하는 state update는 다시 startTransition으로 감싸야 Transition으로 처리된다.
  • 이 제약을 놓치면 pending indicator가 너무 빨리 꺼지거나, 후속 update가 urgent update처럼 동작해 Suspense fallback flicker나 입력 지연을 유발할 수 있다.

  • Transition은 “취소 가능한 작업”이라기보다 “중단되고 재시작될 수 있는 렌더링 우선순위 표시”에 가깝다.

  • 사용자가 입력을 계속하면 이전 Transition 렌더링은 버려질 수 있다.
  • 따라서 Transition 안에서 렌더링이 여러 번 재시도되어도 안전해야 한다.
  • render phase에서 외부 mutation, analytics 전송, imperative side effect를 수행하면 interruption-safe하지 않다.

  • useDeferredValue(value)는 최신 값을 즉시 하위 트리에 전달하지 않고, 이전 값을 잠시 유지할 수 있게 한다.

  • React는 먼저 old deferred value로 화면을 즉시 갱신하고, 이후 background render에서 new value를 적용하려고 시도한다.
  • background render가 suspend되면 이미 보이는 UI를 fallback으로 숨기지 않고, 준비될 때까지 stale UI를 유지할 수 있다.
  • 이 때문에 search results UI에서는 새 검색어와 이전 결과가 잠시 불일치할 수 있으며, 공식 문서도 opacity 조정 등으로 stale 상태를 표시하는 패턴을 제시한다.

  • useDeferredValue는 debounce가 아니다.

  • 고정 시간 지연을 보장하지 않는다.
  • network request 자체를 줄여 주거나 취소하지 않는다.
  • 모든 keystroke에 대해 fetch가 발생할 수 있으며, 단지 렌더링 결과 표시가 지연될 수 있다.
  • 따라서 request deduplication, cache, abort, stale response 무시 로직은 별도로 설계해야 한다.

  • Suspense와 Transition을 함께 사용할 때의 계약:

  • Transition 중에는 이미 reveal된 UI를 불필요하게 fallback으로 숨기지 않도록 설계되어 있다.
  • 하지만 새로 mount되는 Suspense boundary나 잘못 배치된 boundary에서는 fallback이 보일 수 있다.
  • pending state와 fallback state를 같은 의미로 취급하면 UX가 흔들린다. pending은 “background work 진행 중”이고 fallback은 “해당 boundary가 표시할 준비가 안 됨”이다.

  • interruption-safe pending-state 설계 원칙:

  • isPending은 global loading flag처럼 쓰지 말고 특정 Transition에 대한 UI hint로 제한한다.
  • async action의 post-await update를 다시 startTransition으로 감싼다.
  • stale results를 의도적으로 표시한다면 시각적으로 구분한다.
  • stale network response는 렌더링 우선순위와 별개로 방어한다.
  • Transition 내부 렌더링은 idempotent해야 하며 side effect는 effect/event/action 경계로 이동한다.
  • 입력값 자체는 urgent state로 유지하고, 비싼 결과 리스트나 route/content update만 Transition 또는 deferred value로 낮춘다.

Cautions#

  • React concurrent rendering은 public API의 동작 계약을 기준으로 사용해야 하며, 내부 scheduler 구현 세부사항에 의존하면 안 된다.
  • startTransition은 async 함수 전체를 자동으로 Transition 처리한다고 가정하면 위험하다. 공식 문서상 await 이후 update를 다시 감싸야 하는 제한이 있다.
  • useDeferredValue는 race condition 해결책이 아니다. 오래된 fetch response가 늦게 도착해 최신 UI를 덮는 문제는 별도의 abort, sequence check, cache layer로 막아야 한다.
  • isPending은 정확한 “네트워크 요청 진행 중” 상태가 아니다. Transition rendering pending과 server request pending은 다를 수 있다.
  • Suspense fallback flicker는 Transition만으로 항상 사라지지 않는다. Suspense boundary 위치, 이미 reveal된 content인지 여부, 새 boundary mount 여부에 따라 결과가 달라진다.
  • 여러 Transition이 동시에 있을 때 React가 현재 batching할 수 있다는 제한이 문서에 언급되어 있으므로, Transition별 세밀한 pending attribution이 필요하면 별도 상태 설계가 필요하다.

Sources#

  • https://react.dev/reference/react/startTransition
  • https://react.dev/reference/react/useTransition
  • https://react.dev/reference/react/useDeferredValue
  • https://react.dev/reference/react/Suspense
  • https://react.dev/blog/2022/03/29/react-v18
  • https://react.dev/reference/react/useOptimistic

Sagwan Revalidation 2026-07-29T09:14:32Z#

  • verdict: ok
  • note: 현재 React 문서와 일치하며 await 이후 재감싸기 caveat도 유효함

Sagwan Revalidation 2026-07-31T17:48:43Z#

  • verdict: ok
  • note: React 19 기준으로도 async 경계·Transition 제약 설명이 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1