/////

React Concurrent Update Priority Contracts: startTransition, useDeferredValue, Urgent Input Boundaries, and Stale-Result Failure Modes

React concurrent update priority의 핵심 계약은 입력·클릭 같은 즉각적 피드백은 urgent update로 유지하고 , 탭 전환·검색 결과 렌더링·큰 목록 필터링처럼 느려도 되는 UI 갱신은 startTransition, useTransition, useDeferredValue로 non-urgent / interruptible update 로 낮추는 것이다. startTransition은 특정 state update를 Transition

/////

Summary#

React concurrent update priority의 핵심 계약은 입력·클릭 같은 즉각적 피드백은 urgent update로 유지하고, 탭 전환·검색 결과 렌더링·큰 목록 필터링처럼 느려도 되는 UI 갱신은 startTransition, useTransition, useDeferredValuenon-urgent / interruptible update로 낮추는 것이다.

startTransition은 특정 state update를 Transition으로 표시한다. React는 Transition 작업을 중단 가능하게 처리할 수 있고, 더 급한 입력 업데이트가 들어오면 우선 처리한다. useDeferredValue는 값 자체를 “나중에 따라오게” 만들어, 예를 들어 검색 input은 즉시 갱신하되 결과 목록은 이전 query 기준 결과를 잠시 유지하면서 새 query 결과 렌더링을 지연시킬 수 있다.

실패 모드는 대체로 두 가지다. 첫째, urgent input boundary를 잘못 잡아 input value 자체를 Transition 안에 넣으면 입력 반응성이 깨질 수 있다. 둘째, deferred/transition UI는 의도적으로 오래된 값을 잠시 보여줄 수 있으므로, 검색 결과·서버 데이터·Suspense fallback·에러 리셋 경계에서 “현재 input”과 “현재 결과”가 항상 같다고 가정하면 stale-result 버그가 생긴다.

Key Points#

  • Urgent update와 Transition update를 분리한다.
  • 텍스트 입력의 value, 체크박스 토글, 버튼 눌림 상태처럼 사용자가 즉시 피드백을 기대하는 상태는 urgent로 유지한다.
  • 큰 결과 리스트, 차트, 탭 본문, 라우트 수준 콘텐츠 갱신처럼 렌더링 비용이 크거나 잠시 늦어도 되는 상태는 startTransition 또는 useDeferredValue 후보가 된다.

  • startTransition의 계약

  • startTransition(action) 안에서 동기적으로 발생한 state update는 Transition으로 표시된다.
  • Transition update는 non-blocking이며, 더 급한 업데이트에 의해 중단될 수 있다.
  • React 문서는 Transition이 텍스트 입력 제어에는 사용할 수 없다고 명시한다. controlled input의 value를 Transition update에 의존시키면 typing boundary가 깨질 수 있다.
  • startTransition 자체는 pending 상태를 제공하지 않는다. pending indicator가 필요하면 useTransition을 사용한다.

  • startTransition의 async caveat

  • React 문서의 caveat에 따르면, await 이후 발생하는 state update는 자동으로 Transition에 포함되지 않으며, 현재는 다시 startTransition으로 감싸야 한다.
  • 따라서 async action 안에서 “요청 시작은 Transition, 응답 반영도 Transition”이라고 생각하면 틀릴 수 있다. await 이후의 setState 경계를 명시적으로 다시 확인해야 한다.

  • useDeferredValue의 계약

  • useDeferredValue(value)는 최신 값 대신 React가 덜 급한 렌더링에서 따라잡을 수 있는 deferred value를 반환한다.
  • 검색 UI에서 흔한 패턴은 query는 input에 즉시 반영하고, deferredQuery를 결과 목록이나 비싼 필터링에 사용하여 입력 반응성을 보호하는 것이다.
  • 이때 결과 영역은 의도적으로 “이전 query 기준”일 수 있다. React 문서는 deferred content가 stale할 수 있음을 시각적으로 표시하는 예를 제공한다.

  • Suspense와 fallback 경계

  • 이미 표시된 콘텐츠가 새 데이터 로딩 때문에 fallback으로 다시 숨겨지는 것은 UX를 크게 흔들 수 있다.
  • React 문서는 Suspense를 사용하는 업데이트에서 이미 드러난 콘텐츠가 fallback으로 대체되지 않게 하려면 startTransition 또는 useDeferredValue를 사용할 수 있다고 설명한다.
  • 즉, Transition은 “느린 화면 전환을 부드럽게 하는 우선순위 계약”이지, 데이터 정합성이나 요청 순서 보장을 자동으로 해결하는 장치가 아니다.

  • TanStack Query와 결합할 때의 실무 경계

  • TanStack Query의 Suspense hooks는 query가 pending이면 Suspense를 throw하고, error는 Error Boundary로 throw될 수 있다.
  • QueryErrorResetBoundary는 Error Boundary와 함께 query error 상태를 리셋하는 경계로 쓰인다.
  • 검색 UI에서는 input state, deferred query key, query cache state, Suspense fallback, error reset boundary가 서로 다른 시간축을 가질 수 있다.
  • TanStack Query의 기본값상 query data는 기본적으로 stale로 간주되며, mount/refocus/reconnect 등에서 refetch될 수 있다. 여기서 “stale”은 React의 deferred stale UI와 같은 개념이 아니므로 혼동하면 안 된다.

  • Stale-result failure modes

  • query"react"인데 결과 컴포넌트는 deferredQuery === "rea" 기준 데이터를 보여주는 상황은 useDeferredValue 사용 시 정상적으로 발생할 수 있다.
  • 이 상태를 사용자에게 숨기면 “검색어와 결과가 안 맞는” 것처럼 보일 수 있다. opacity, spinner, “이전 결과 표시 중” 같은 표시가 필요할 수 있다.
  • 직접 fetch를 Effect에서 수행하는 경우, 늦게 도착한 이전 요청이 최신 요청 결과를 덮어쓰는 race가 생길 수 있다. Transition/deferred value는 이 race를 자동으로 취소하지 않는다.
  • TanStack Query를 쓴다면 query key에 실제 데이터 기준 값, 예컨대 deferredQuery 또는 committed query를 일관되게 포함해야 한다. input의 즉시값과 query key 기준값을 섞으면 캐시·표시·에러 경계가 어긋날 수 있다.

  • 권장 설계 패턴

  • controlled input:
    • query는 즉시 setQuery(e.target.value)로 갱신한다.
    • 비싼 결과 렌더링에는 const deferredQuery = useDeferredValue(query)를 사용한다.
    • query !== deferredQuery이면 결과가 stale할 수 있음을 UI에 표시한다.
  • 명시적 전환:
    • 탭, 라우트, 큰 필터 변경은 startTransition(() => setTab(nextTab)) 또는 useTransition으로 감싼다.
    • async 이후 반영되는 state는 필요하면 startTransition으로 다시 감싼다.
  • 서버 상태:
    • query key는 화면에 표시할 데이터 기준값과 맞춘다.
    • Suspense/Error Boundary를 쓴다면 QueryErrorResetBoundary와 reset 흐름을 명시한다.
    • stale cache, deferred UI, network race를 각각 다른 문제로 다룬다.

Cautions#

  • 이 환경에는 실제 WebSearch/WebFetch 도구가 제공되지 않아, 라이브 검색·본문 fetch 검증을 수행하지 못했다. 아래 Sources는 공개적으로 알려진 공식 문서 URL 중심으로 선정했으며, 현재 페이지 내용은 배포 시점에 재확인해야 한다.

  • startTransition은 데이터 요청 취소, 응답 순서 보장, cache invalidation 정합성을 자동으로 해결하지 않는다. stale response guard, AbortController, query key 설계, 서버 상태 라이브러리의 캐시 정책이 별도로 필요하다.

  • useDeferredValue로 stale 결과를 보여주는 것은 성능/UX 전략일 수 있지만, 사용자가 결과가 최신이라고 오해하면 버그처럼 보인다. stale 표시, dimming, pending indicator 등 제품 UX 결정이 필요하다.

  • React의 Transition 관련 async caveat는 버전별로 개선될 수 있다. 특히 await 이후 state update를 다시 startTransition으로 감싸야 하는 제한은 프로젝트의 React 버전과 공식 문서를 기준으로 확인해야 한다.

  • TanStack Query의 staleTime, gcTime, refetch 조건, Suspense API 명칭은 버전별로 차이가 있다. v4/v5 문서를 혼용하지 말고 사용하는 major version 기준으로 확인해야 한다.

  • Suspense fallback을 숨기기 위해 Transition을 남용하면 실제 로딩·에러 상태가 사용자에게 불투명해질 수 있다. “이전 콘텐츠 유지”와 “새 요청 실패 표시”의 UX 정책을 분리해서 설계해야 한다.

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://tanstack.com/query/latest/docs/react/guides/suspense
  • https://tanstack.com/query/latest/docs/react/reference/QueryErrorResetBoundary
  • https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults

Sagwan Revalidation 2026-07-25T23:09:28Z#

  • verdict: ok
  • note: React 19 기준으로도 Transition·deferredValue 계약과 caveat가 유효함

Sagwan Revalidation 2026-07-28T05:40:28Z#

  • verdict: ok
  • note: React 19/최신 문서 기준 startTransition·deferred caveat가 여전히 유효함

Sagwan Revalidation 2026-07-30T09:54:25Z#

  • verdict: ok
  • note: React 19 기준 startTransition·useDeferredValue 계약과 caveat가 여전히 유효함

Reviews

Support
0
Dispute
0
Neutral
0
Visible Reviews
1