Integrating View Transitions with React Router
Use React Router 6.4's built-in unstable_useViewTransitionState hook to animate route changes — including manual coordination via flushSync for older versions.
Detailed Explanation
React Router 6.4+ Built-In Support
Since React Router 6.4 (March 2024), every <Link> accepts a viewTransition prop:
import { Link } from 'react-router-dom';
<Link to="/products/42" viewTransition>
Product 42
</Link>
When the user clicks, React Router internally wraps its navigation in document.startViewTransition(). No further setup needed.
Conditional styling per active transition
To style the transitioning route differently, use the useViewTransitionState hook:
import { useViewTransitionState } from 'react-router-dom';
function ProductCard({ id }) {
const isTransitioning = useViewTransitionState(`/products/${id}`);
return (
<div style={{ viewTransitionName: isTransitioning ? `product-${id}` : 'none' }}>
…
</div>
);
}
This dynamically opts the card in only when it is the source of the current transition, avoiding the unique-name explosion on the entire grid.
Manual integration without the hook (older React Router)
import { flushSync } from 'react-dom';
import { useNavigate } from 'react-router-dom';
function MyLink({ to, children }) {
const navigate = useNavigate();
return (
<a onClick={(e) => {
e.preventDefault();
if (!document.startViewTransition) return navigate(to);
document.startViewTransition(() => {
flushSync(() => navigate(to));
});
}}>{children}</a>
);
}
The flushSync forces React to commit the navigation synchronously so the browser can take its second snapshot immediately. Without it, React batches the update and the snapshot is taken too early.
Suspense boundaries
If the route uses <Suspense>, the transition will wait for the Suspense boundary to resolve before snapshotting. Render a meaningful skeleton inside Suspense so the cross-fade animates between two real-looking views, not between content and a spinner.
Use Case
Production React apps using React Router 6.4+ for navigation. Especially powerful for product detail pages, photo gallery deep-links, and any 'list → detail → list' flow where the same item appears in different layouts.