Shots in this scene
View Transitions in React
In one line
React's own <ViewTransition> component is not in the stable React 19.2.8. What works today is document.startViewTransition with flushSync inside the callback, so React has rendered before the browser takes the new snapshot.
Think of it like this
A photographer must take the "after" picture only when the room has been rearranged. If the movers are still carrying the sofa, the "after" looks like the "before". In React the movers are the renderer: wait for it before the browser takes its picture.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- flushSync
- A
react-domfunction that makes React render and commit the update inside it before it returns. - commit
- The moment React writes its result into the real DOM.
- canary channel
- React's pre-release channel for features not yet in a stable release.
- race
- A bug that depends on which of two things finishes first, so it works or fails from run to run.
Step by step
01Check what the installed React exports
Before trusting an article, look at the version you use:
02Flush inside the callback
Press Reverse (flushSync) and slow the timeline: four group(item-…) tracks appear and the items travel.
03See the race
Press Reverse (startTransition) a few times: the page text says how many items moved, mostly 4 and now and then 0. The console prints only the flushSync line, because it is the stable one.
04Press Add
Add (plain setState) appends an item with no transition and no tracks: only changes you route through startViewTransition animate.
Try it yourself
- 1
Take the 40 ms away
Delete
slow(40)from the list. In eight runsstartTransitionthen showed 4 of 4 every time: fine in a small example, now and then wrong with a heavy list.
Code & diagrams
Names come from state. The page text shows how many items moved in each case; the startTransition result varies from run to run.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
with flushSync: 4 of 4 items moveBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Let React render later, on a heavy list
Replace flushSync(reverse) with startTransition(reverse) and keep slow(40).
Myth vs fact
Myth
React handles view transitions for me.
Fact
Not in stable React 19.2.8: the component exists only in canary builds. The browser API plus flushSync is the stable way.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
flushSyncforces a synchronous render, normally something to avoid. Inside a transition callback it is the point: the browser is already waiting. Keep the change small (a render slower than about 4 seconds loses the animation, Topic 11.1), and call it from handlers and the callback, not from effects or renders. - ▸
Motion's
layoutandAnimatePresenceanimate inside React's lifecycle and never freeze the page, so they suit shared elements, enter and exit, gestures and springs (Phase 15).
Remember this
- 1
The problem this solves:
startViewTransition(update)expects the DOM to be changed whenupdatereturns. React does not change the DOM onsetState: it schedules a render. SostartViewTransition(() => setItems(next))can take the new picture before React has drawn anything, and nothing animates. - 2
What exists in React 19.2.8. The installed package's exports include
Activity,startTransition,useTransitionandunstable_useCacheRefresh, and **noViewTransition**: the component exists only in the canary and experimental channels, so do not import it in production code. Topic 11.6 of the React course (/react) says the same. - 3
The pattern.
flushSync(() => setItems(next))fromreact-dommakes React render and commit before it returns. Put it inside the update:document.startViewTransition(() => { flushSync(() => setState(next)); }). Name the elements from state:style={{ viewTransitionName: "item-" + id }}. - 4
**Without
flushSyncit is a race.** We reversed four items, each render taking 40 ms. WithflushSync, 4 of 4 items moved in every run. WithstartTransition(reverse)(React renders in slices, later) four of six runs showed 4 of 4 and two showed 0 of 4 (the two pictures were identical). With the 40 ms removed, all eight runs showed 4 of 4. A plainreverse()in the callback also showed 4 of 4 in all 13 runs here, but React does not promise when it renders then, so onlyflushSyncis safe. A race hides until the page gets heavy. - 5
Only changes you route through the helper animate. A plain
setItems(the Add button) just updates: no snapshot, no paused rendering, no blocked clicks. For motion inside components (shared elements that stay mounted, enter and exit, gestures, springs) Motion'slayoutandAnimatePresence(Phase 15) work within React's lifecycle. Use view transitions for whole-view changes.
Explain it without notes
Why does document.startViewTransition(() => setState(next)) not reliably animate in React, and does React 19.2.8 export <ViewTransition>?
When would you reach for Motion instead of the browser API?
Practice
Write an animate(change) helper for a React app that falls back when the API is missing, respects reduced motion and never loses the state change.
Name the cards of a product grid from their ids so a sort animates, and say what key={index} would break.
Done when you can
You can say what React 19.2.8 exports for view transitions and how to check
You can write the
flushSyncpattern with a fallbackYou can name list items from state and say why index names fail