React 19.3 Tutorial: 11 Steps to Ship in 60 Min [2026]
![React 19.3 Tutorial: 11 Steps to Ship in 60 Min [2026]](https://trendintech.com/wp-content/uploads/2026/10/wpshim-2221657-react-19-3-tutorial-view-transitions-fragment-refs-2026.webp)
React 19.3 shipped on npm on September 9, 2026, and it changes how you’ll write animations, refs, and server-aware components in everyday React code. If you’ve been putting off the upgrade, or you just want a working project that actually uses the new APIs instead of a changelog summary, this tutorial walks through all of it: install, migrate, build, test, and ship. By the end you’ll have a small but complete React 19.3 app wired up with <ViewTransition>, Fragment Refs, browser(), and Trusted Types, plus the troubleshooting notes you’ll need when something doesn’t behave the way the docs imply.
This guide targets developers who already know basic React (components, hooks, JSX) but haven’t touched the 19.3 release yet. Total build time: roughly 45-60 minutes if you follow along step by step, less if you skim to the sections you need.
Don't miss new tech stories on Google
Add TrendinTech once in the Google app and our stories appear in your news suggestions.
What’s actually new in React 19.3
The official React 19.3 release post lists four headline additions: View Transitions, Fragment Refs, the browser() API, and Trusted Types support, alongside Context support inside Server Components. None of these are breaking changes — React 19.3 is a minor version bump from 19.2, and existing 19.x apps keep working without modification. That matters for planning your upgrade: you don’t need a big-bang rewrite, you can adopt each feature where it actually helps.
Here’s the short version of each feature, before we build with them:
- View Transitions — a new
<ViewTransition>component that hooks into the browser’s native View Transition API so elements animate in, out, and across the page without a separate animation library. - Fragment Refs — you can now pass a
refto<Fragment>and get a handle on a group of DOM children, even when there’s no wrapper element to attach the ref to. browser()— a function that lets a component explicitly opt out of server rendering when server rendering doesn’t make sense for it (think: components that depend entirely onwindowor browser-only APIs).- Trusted Types support — React can now be configured to satisfy a page’s Trusted Types Content Security Policy, which locks down how strings get turned into DOM, script, or URL content.
- Context in Server Components — Server Components can now read Context values, closing a gap that previously forced prop drilling across the server/client boundary.
According to the React team, “React 19.3 is now available! This release makes View Transitions and Fragment Refs stable, and adds browser(), Trusted Types support, and Context in Server Components” (React, official announcement). The word “stable” is doing real work there — both View Transitions and Fragment Refs existed in earlier experimental builds, and 19.3 is the release where they become safe to use in production code.
Prerequisites and versions
Confirm these before you start. Version mismatches are the single biggest cause of “the tutorial doesn’t work” reports on React upgrade threads, so don’t skip this.
| Requirement | Minimum version | Notes |
|---|---|---|
| Node.js | 20.x or later (22.x recommended) | React 19.3’s tooling and Vite both expect a current LTS Node release |
| npm | 10.x or later | Comes bundled with recent Node installs |
| react / react-dom | 19.3.0 | Released September 9, 2026 — check with npm ls react |
| Vite | 6.x or later | Used as the build tool in this tutorial; CRA is no longer maintained |
| TypeScript (optional) | 5.6 or later | Needed if you want typed Fragment Refs and ViewTransition props |
| Browser for testing | Chrome 126+ or Edge 126+ | View Transitions API support is still inconsistent across engines — see the browser support section below |
| Code editor | VS Code or Cursor | Either works; this tutorial doesn’t depend on editor-specific tooling |
You’ll also want basic comfort with hooks (useState, useEffect, useRef) and the command line. If you’re starting from React 18, read the upgrade notes section below before jumping into the new APIs — a couple of 19.0 changes (the new JSX transform requirements, ref-as-prop handling) will bite you if you skip straight to 19.3 features.
Step 1: Scaffold a fresh React 19.3 project
Starting clean is the fastest way to confirm your toolchain works before you touch any migration code. Use Vite’s React-TS template:
npm create vite@latest react-193-tutorial -- --template react-ts
cd react-193-tutorial
npm install
npm install [email protected] [email protected]
npm run dev
Open the local dev server URL Vite prints (typically http://localhost:5173) and confirm the default counter app renders. Then check your installed version:
npm ls react react-dom
# Expect:
# [email protected]
# [email protected]
If npm resolves an older patch version, pin it explicitly in package.json with "react": "19.3.0" and "react-dom": "19.3.0", delete node_modules and package-lock.json, then reinstall. React’s own 19.3 release notes confirm the package is distributed through npm the same way as prior 19.x releases, so there’s no special install flow or separate CLI.
Step 2: Upgrading an existing React 18 or 19.x app
If you’re migrating a real app rather than starting fresh, upgrade in two hops: React 18 to 19.0 first, then 19.0 to 19.3. Jumping straight from 18 to 19.3 technically works for most apps, but debugging is easier if you isolate the 19.0 breaking changes from the 19.3 feature additions.
# Step A: land on React 18.3 first (helps surface deprecation warnings)
npm install [email protected] [email protected]
# Step B: run the official codemod to catch breaking API usage
npx codemod@latest react/19/migration-recipe
# Step C: move to the current 19.3 release
npm install [email protected] [email protected]
Watch for three common 19.0 breaking changes the codemod catches: removed propTypes and defaultProps support on function components, the new requirement that ref is passed as a regular prop instead of through forwardRef in most cases, and changes to how react-dom/test-utils is imported. None of these are new to 19.3, but they’ll surface as errors the first time you touch an old codebase.
Once the codemod passes and the app builds clean on 19.0 or 19.2, bumping to 19.3.0 should not introduce new breaking changes — it’s additive. Run your existing test suite after each hop, not just at the end, so a regression doesn’t get buried under three version bumps’ worth of diffs.
Step 3: Build the project shell
The working project for this tutorial is a small “task board” app: a card list where cards animate when reordered (View Transitions), a toolbar that measures a cluster of buttons without a wrapper div (Fragment Refs), a widget that explicitly skips server rendering (browser()), and a sanitized note field (Trusted Types). It’s intentionally compact so every new API gets exercised without a pile of boilerplate.
Replace the contents of src/App.tsx with this shell, then we’ll add each feature on top of it:
import { useState } from "react";
import "./App.css";
type Task = { id: string; title: string; done: boolean };
const initialTasks: Task[] = [
{ id: "t1", title: "Draft release notes", done: false },
{ id: "t2", title: "Update Storybook", done: false },
{ id: "t3", title: "Review PR #482", done: false },
];
export default function App() {
const [tasks, setTasks] = useState(initialTasks);
return (
Task Board
{tasks.map((task) => (
- {task.title}
))}
);
}
Confirm this renders a plain list before moving on. If it doesn’t, fix that first — layering new APIs on top of a broken base is how debugging sessions spiral.
Step 4: Animate list reordering with <ViewTransition>
The new <ViewTransition> component wraps elements so React can animate them as they enter, exit, move, or resize, using the browser’s native View Transition API under the hood. Per the ViewTransition reference docs, “<ViewTransition> lets you animate a component tree with Transitions and Suspense.” That’s the key constraint: it animates changes that happen inside a React Transition (via startTransition or actions), not every state update automatically.
Add a “move to top” button and wrap the list in <ViewTransition>:
import { useState, useTransition, unstable_ViewTransition as ViewTransition } from "react";
import "./App.css";
type Task = { id: string; title: string; done: boolean };
const initialTasks: Task[] = [
{ id: "t1", title: "Draft release notes", done: false },
{ id: "t2", title: "Update Storybook", done: false },
{ id: "t3", title: "Review PR #482", done: false },
];
export default function App() {
const [tasks, setTasks] = useState(initialTasks);
const [isPending, startTransition] = useTransition();
function moveToTop(id: string) {
startTransition(() => {
setTasks((current) => {
const target = current.find((t) => t.id === id)!;
const rest = current.filter((t) => t.id !== id);
return [target, ...rest];
});
});
}
return (
Task Board
{tasks.map((task) => (
-
{task.title}
))}
);
}
Click “Move to top” on the second or third item and the list should visibly slide into its new order instead of snapping. If you don’t see animation, check two things: the state update must happen inside startTransition, and your browser must support the View Transition API (see the browser support table in the troubleshooting section).
Output example — console and DOM behavior you should see on a supporting browser:
// No console errors on click
// DOM: the clicked <li> animates from its old position to the top
// over ~250ms using the browser's default view-transition easing
Step 5: Group DOM elements with Fragment Refs
Before 19.3, if you needed a single ref over a group of sibling elements, you had to wrap them in a <div> just to have somewhere to attach the ref — even when that wrapper broke your CSS grid or flex layout. Fragment Refs remove that workaround. As React’s docs put it, “Fragment Refs solve these problems by providing a limited set of commonly used DOM methods that work with any React component, regardless of what it renders” (React 19.3 release notes).
Add a toolbar of three buttons, grouped with a Fragment ref so you can measure their combined bounding box without an extra wrapper element:
import { Fragment, useRef, useState } from "react";
function Toolbar() {
const toolbarRef = useRef | null>(null);
const [width, setWidth] = useState(null);
function measure() {
const rect = toolbarRef.current?.getBoundingClientRect();
if (rect) setWidth(Math.round(rect.width));
}
return (
{width !== null && Combined toolbar width: {width}px
}
);
}
The React team’s own framing matters here: “Pass a ref to <Fragment> to work with its DOM children as a group, without adding a wrapper element” (React, official announcement). The ref gives you a subset of DOM methods — things like getBoundingClientRect, focus, and querying — rather than a full element reference, because there’s no single underlying DOM node to hand back.
Drop <Toolbar /> into App.tsx above the task list and click “Measure toolbar.” You should get a pixel width back without any wrapper <div> showing up in your DevTools Elements panel.
Step 6: Opt a component out of server rendering with browser()
browser() is aimed at components that genuinely can’t do anything useful on the server — things built entirely around window, localStorage, canvas APIs, or other browser-only globals. Instead of guarding every line with typeof window !== "undefined" checks, you call browser() to tell React this component should skip server rendering outright and render only on the client.
import { browser } from "react";
import { useEffect, useState } from "react";
function LocalTaskCount() {
browser();
const [count, setCount] = useState(0);
useEffect(() => {
const stored = window.localStorage.getItem("task-count");
setCount(stored ? Number(stored) : 0);
}, []);
return Tasks stored locally: {count}
;
}
In a client-only Vite app like the one we scaffolded, you won’t see a visible difference since there’s no server render happening in the first place. The practical use case shows up once you add a server-rendering framework (Next.js App Router or a custom React Server Components setup): without browser(), a component touching window at module scope throws during the server pass. With it, React knows to skip straight to client rendering for that subtree instead of crashing the whole page.
If you want to see the effect in context, the companion piece to this is Context support in Server Components — also new in 19.3 — which means a Server Component can now read a Context value set higher in the tree instead of needing that value passed down as a prop through every layer. Combined with browser(), you get a cleaner split: Server Components read shared Context for data, client-only components opt out cleanly when they need the DOM.
Step 7: Satisfy a Trusted Types CSP
Trusted Types is a browser security feature (see MDN’s Trusted Types API docs) that blocks raw strings from being assigned to sensitive DOM sinks like innerHTML unless they’ve been wrapped in a policy-approved object. Pages that enforce a strict Trusted Types Content-Security-Policy would previously break React apps that rely on any internal string-to-DOM assignment. React 19.3 adds support for configuring a policy React itself will use, so your app can run under a Trusted Types CSP without forking React internals.
Add a note field to the task board that renders user-supplied text, and configure a Trusted Types policy around it:
// trustedTypesSetup.ts — import this once, before React renders
if (typeof window !== "undefined" && window.trustedTypes) {
window.trustedTypes.createPolicy("react-app-policy", {
createHTML: (input: string) => input,
createScript: () => "",
createScriptURL: () => "",
});
}
# To test this locally, serve the app with a strict CSP header:
Content-Security-Policy: require-trusted-types-for 'script'; trusted-types react-app-policy
Import trustedTypesSetup.ts at the top of main.tsx, before ReactDOM.createRoot runs. Without it, a page serving that CSP header will throw a TrustedTypesPolicyViolation the moment React tries to touch the DOM. With the policy registered, React’s internal DOM writes route through it and the page loads clean. This matters most for teams in regulated industries or anyone running a strict CSP as part of a security hardening pass — it’s not something a typical hobby project needs, but it’s the kind of thing that blocks an entire migration if you hit it unprepared.
Step 8: Wire it all together in the task board
With all four pieces built, assemble the final App.tsx:
import { Fragment, useRef, useState, useTransition, unstable_ViewTransition as ViewTransition } from "react";
import "./App.css";
type Task = { id: string; title: string; done: boolean };
const initialTasks: Task[] = [
{ id: "t1", title: "Draft release notes", done: false },
{ id: "t2", title: "Update Storybook", done: false },
{ id: "t3", title: "Review PR #482", done: false },
];
function Toolbar() {
const toolbarRef = useRef | null>(null);
const [width, setWidth] = useState(null);
function measure() {
const rect = toolbarRef.current?.getBoundingClientRect();
if (rect) setWidth(Math.round(rect.width));
}
return (
{width !== null && Combined toolbar width: {width}px
}
);
}
export default function App() {
const [tasks, setTasks] = useState(initialTasks);
const [isPending, startTransition] = useTransition();
const [note, setNote] = useState("");
function moveToTop(id: string) {
startTransition(() => {
setTasks((current) => {
const target = current.find((t) => t.id === id)!;
const rest = current.filter((t) => t.id !== id);
return [target, ...rest];
});
});
}
return (
Task Board
{tasks.map((task) => (
-
{task.title}
))}
);
}
Run npm run dev again and walk through all four behaviors: reorder a task and watch it animate, measure the toolbar without a wrapper div in the DOM tree, confirm the note field accepts text, and (if you set up the CSP header) confirm no Trusted Types violations show up in the console.
Step 9: Write tests for the new APIs
Use React Testing Library for component tests — it works unchanged with React 19.3, since none of the new APIs alter the public rendering contract that Testing Library depends on. Install it if you haven’t:
npm install -D @testing-library/react @testing-library/jest-dom vitest jsdom
A basic test for the “move to top” transition behavior:
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import App from "./App";
describe("Task Board", () => {
it("moves a task to the top of the list on click", async () => {
render( );
const buttons = screen.getAllByText("Move to top");
fireEvent.click(buttons[1]); // move the second task
const items = screen.getAllByRole("listitem");
expect(items[0]).toHaveTextContent("Update Storybook");
});
});
Testing Library intentionally doesn’t assert on the View Transition animation itself — jsdom doesn’t implement the browser’s View Transition API, so your tests should check the resulting state and DOM order, not the animation frames. Save animation verification for manual QA or an end-to-end tool like Playwright running in a real Chromium browser.
Step 10: Check browser support before you ship
View Transitions is the feature most likely to behave differently across browsers, because React is delegating to the native View Transition API rather than reimplementing animation itself. Check current support on caniuse.com/view-transitions before depending on it for anything critical to the user flow.
| Feature | Chrome/Edge | Firefox | Safari | Fallback behavior |
|---|---|---|---|---|
| <ViewTransition> | Supported | Partial/behind flags in some versions | Partial | Content still updates correctly; it just snaps instead of animating |
| Fragment Refs | Supported | Supported | Supported | Pure JS API, no browser feature dependency |
| browser() | Supported | Supported | Supported | React-internal API, works anywhere React runs |
| Trusted Types | Supported | Not supported natively | Not supported natively | CSP directive is simply ignored on non-supporting browsers |
This is the single most important thing to internalize about View Transitions specifically: it’s a progressive enhancement. React doesn’t throw or break functionality on browsers without native support — the UI just updates instantly without the animation. Don’t build any feature where the animation itself carries information the user needs; treat it purely as polish.
Step 11: Build for production and verify the bundle
Before deploying, run a production build and check that nothing in the new APIs balloons your bundle size or breaks under minification. None of the four 19.3 additions pull in extra runtime dependencies — they’re part of the core react and react-dom packages — but it’s still worth confirming the build output looks sane, especially the first time you add a Trusted Types policy file.
npm run build
npm run preview
Open the preview URL and repeat the same manual checks from Step 8: the task reorder animation, the toolbar measurement, and the note field. A production build strips development warnings, so if something only works in dev, this is where you’ll find out — most commonly because a dev-only check (like a console.warn guard) was masking a real issue.
If you’re deploying to a platform like Vercel, Netlify, or a static host behind a CDN, double check whatever security headers your hosting config applies. Some platforms inject a default Content-Security-Policy or add X-Content-Type-Options headers that can interact with the Trusted Types setup from Step 7 in ways your local dev server never surfaced. Test the deployed preview URL specifically for Trusted Types violations in the browser console before promoting to production, since a CSP mismatch between your local vite.config.ts dev server and your hosting platform’s edge configuration is a common source of “it worked locally” bugs.
For teams running continuous integration, add a quick smoke check to your pipeline that runs the Vitest suite from Step 9 against the production build output, not just the dev build — this catches minification-related issues (like a bundler renaming something browser() depends on internally) before they reach users.
Common pitfalls when adopting React 19.3
These are the mistakes most likely to cost you an afternoon:
- Forgetting startTransition. Wrapping elements in
<ViewTransition>does nothing if the state change that reorders them happens in a plainsetStatecall outside a Transition. The animation only triggers for updates React treats as a Transition. - Expecting Fragment Refs to behave like element refs. A Fragment ref gives you a limited subset of DOM methods for the group of children, not a reference to a single DOM node. Code that expects
ref.currentto be anHTMLElementdirectly will fail type checks and runtime checks alike. - Calling browser() conditionally. Like hooks,
browser()needs to run unconditionally at the top of the component on every render for React to track it consistently. Wrapping it in anifstatement produces inconsistent behavior between renders. - Skipping the React 18→19.0 codemod. Jumping straight to 19.3 from an older 18.x codebase without running the migration codemod means you’ll hit 19.0’s breaking changes (removed PropTypes, ref-as-prop) at the same time as learning the new 19.3 APIs, which makes debugging much harder to isolate.
- Assuming Trusted Types is required. Unless your organization already enforces a Trusted Types CSP, you don’t need to add a policy. Adding one unnecessarily can break third-party scripts that aren’t Trusted-Types-aware.
- Testing View Transitions in jsdom. jsdom has no View Transition API implementation. Tests that try to assert on animation state in a unit test environment will either silently no-op or throw, depending on your setup — assert on final DOM state instead.
- Mixing npm install flags. Running
npm install react@latestwithout pinningreact-domto the same version can leave you with mismatched major/minor versions between the two packages, which throws a cryptic “Incompatible React versions” error at runtime.
Troubleshooting guide
Issues you’re likely to hit, in the order you’re likely to hit them:
- “npm install” resolves react 19.2 instead of 19.3. Your lockfile or a cached registry metadata entry is stale. Run
npm cache clean --force, deletepackage-lock.jsonandnode_modules, then reinstall with the version pinned explicitly inpackage.json. - <ViewTransition> doesn’t animate at all. First confirm the update is wrapped in
startTransition. Second, confirm your browser supports the View Transition API (check caniuse). Third, check that you’re not rendering a brand-new list of elements with new keys each time — View Transitions needs stable keys to know which old element maps to which new one. - TypeScript complains about
unstable_ViewTransition. The export is still prefixedunstable_in the current release even though the feature itself is stable behavior — that’s a naming carryover from the experimental builds, not a bug in your code. Import it as shown in Step 4. - Fragment ref is always null. Confirm you’re passing
refdirectly to<Fragment>, not to a child inside it. Also confirm the Fragment actually renders DOM children — a Fragment that renders onlynullor other Fragments has nothing to attach to. - “browser is not a function” import error. Confirm you’re on
react@19.3.0exactly —browser()doesn’t exist in 19.0-19.2. Runnpm ls reactto double check. - TrustedTypesPolicyViolation errors in the console. Your CSP header is active but the policy name in your header doesn’t match the policy name you registered with
createPolicy(). The strings must match exactly, including case. - App works in dev but breaks in production build. Check whether your production CSP headers differ from your dev server’s headers — Trusted Types issues frequently only appear once a stricter production CSP is applied via your hosting platform or CDN.
- Tests fail with “ViewTransition is not a valid child type.” This usually means your testing environment is resolving an older React version than your app code. Check for a duplicate React install in
node_moduleswithnpm ls react— monorepos and certain peer dependency setups can end up with two copies. - Hot reload breaks after adding browser(). Some Vite HMR setups re-run module-scope code unpredictably. If you see stale behavior after editing a component that calls
browser(), do a full page refresh rather than relying on HMR for that file during development.
Advanced tips for production use
A few things worth knowing once the basics work:
- Scope View Transitions narrowly. Wrapping your entire app in one top-level
<ViewTransition>sounds convenient but makes every unrelated update animate together, which usually looks worse than intentional, scoped transitions on just the components that benefit. - Combine Fragment Refs with ResizeObserver. Since a Fragment ref exposes
getBoundingClientRect, you can pair it with aResizeObserverto react to layout changes in a group of elements without ever introducing a wrapper node that could break a CSS Grid or Flexbox layout. - Use browser() at the boundary, not throughout. Call it once in the outermost component that’s genuinely client-only, rather than scattering it across every child — this keeps the server/client split easy to reason about later.
- Audit third-party scripts before enabling Trusted Types in production. Any analytics tag, chat widget, or ad script that writes HTML strings directly will violate a strict Trusted Types CSP unless it ships its own compliant policy. Test with the CSP in report-only mode first:
Content-Security-Policy-Report-Only. - Read Context in Server Components deliberately. Just because Server Components can now read Context doesn’t mean every piece of app state belongs there — Context in a Server Component is evaluated once per server render, not reactively, so it’s suited to configuration-style values, not frequently changing client state.
Complete working project reference
Your final project structure after following every step above:
react-193-tutorial/
├── src/
│ ├── App.tsx # Task board: ViewTransition + Fragment Ref + browser() + textarea
│ ├── App.test.tsx # React Testing Library test for reorder behavior
│ ├── App.css
│ ├── trustedTypesSetup.ts # Trusted Types policy registration
│ └── main.tsx # Imports trustedTypesSetup.ts before createRoot
├── package.json # react & react-dom pinned to 19.3.0
├── vite.config.ts
└── tsconfig.json
Full package.json dependency block for reference:
{
"dependencies": {
"react": "19.3.0",
"react-dom": "19.3.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.0.0",
"@testing-library/react": "^16.0.0",
"typescript": "^5.6.0",
"vite": "^6.0.0",
"vitest": "^2.0.0",
"jsdom": "^25.0.0"
}
}
This is intentionally a small reference project rather than a production template — the goal is to isolate each new 19.3 API so you can see exactly what triggers it and what it returns, before you fold the same patterns into a bigger codebase.
How React 19.3 compares to the 19.0 and 19.2 releases
| Release | Key additions | Breaking changes | Upgrade effort |
|---|---|---|---|
| 19.0 | Actions, useActionState, use() hook, Server Components stabilized | Removed PropTypes/defaultProps on function components, ref-as-prop handling | Moderate — codemod recommended |
| 19.2 | Incremental Server Component improvements, bug fixes | None significant | Low — drop-in |
| 19.3 | ViewTransition, Fragment Refs, browser(), Trusted Types, Context in Server Components | None — fully additive | Low — drop-in, adopt features incrementally |
The practical takeaway: if you’re already on any 19.x release, upgrading to 19.3 is low-risk because it adds APIs rather than changing existing behavior. The real migration work, if you still have it ahead of you, is the 18-to-19.0 jump — that’s where PropTypes removal and the ref-as-prop change actually require touching code.
Where React 19.3 fits in a 2026 web development stack
React remains the framework with the most tutorials and job listings among frontend choices, and that holds through 2026 alongside Vue, Angular, and newer entrants. A typical current web development stack built around React 19.3 pairs it with Vite for bundling, Vitest or Playwright for testing, and a deployment pipeline through a platform like Vercel or a CI/CD tool like GitHub Actions. If your team is also maintaining a desktop build of the same app, note that cross-platform frameworks like Vite-based Tauri setups work fine with React 19.3’s new APIs, since none of the four new features depend on anything browser-vendor-specific beyond View Transitions’ progressive-enhancement fallback.
For API testing against a React 19.3 frontend, nothing about this release changes how you’d approach it — Postman, Bruno, or Hoppscotch all continue to work against whatever backend your components call, since View Transitions, Fragment Refs, browser(), and Trusted Types are all client-rendering concerns that don’t touch your API contract.
Frequently asked questions
Is React 19.3 a breaking release?
No. React 19.3 is additive — it adds View Transitions, Fragment Refs, browser(), Trusted Types support, and Context in Server Components without removing or changing existing 19.x behavior. Apps already on 19.0, 19.1, or 19.2 can upgrade without a migration codemod.
Do I need to rewrite my animations to use <ViewTransition>?
No. Existing animation libraries like Framer Motion or CSS transitions continue to work unchanged. <ViewTransition> is an additional option for cases where you want the browser’s native View Transition API without pulling in another dependency.
Why does my ViewTransition import say “unstable_ViewTransition”?
The export name kept its “unstable_” prefix from earlier experimental builds even though the feature behavior itself is stable in 19.3. Import it as unstable_ViewTransition and alias it to a cleaner name in your own code if you prefer.
Can I use Fragment Refs with class components?
Fragment Refs work at the <Fragment> element level regardless of whether the surrounding component is a function or class component, since the ref attaches to the Fragment’s rendered DOM children, not to the component definition itself.
Does Trusted Types support mean React is now secure by default?
No. Trusted Types support means React can be configured to comply with a page that enforces a Trusted Types CSP. It doesn’t change React’s default behavior for apps that don’t set that CSP header — you have to opt in to the stricter policy yourself.
What happens to browser() in a fully client-rendered app with no server?
Calling browser() in a Vite SPA with no server rendering has no visible effect, since there’s no server pass to opt out of. It becomes relevant once you introduce a server-rendering layer, such as a React Server Components framework.
Will Create React App work with React 19.3?
Create React App is no longer maintained and isn’t recommended for new projects. Use Vite, as shown in this tutorial, or a framework like Next.js if you need server rendering alongside the new 19.3 Server Component Context support.
How long does a typical React 18-to-19.3 migration take for a mid-sized app?
This varies a lot by codebase size and PropTypes usage, but running the official codemod and fixing the flagged issues typically takes a focused day or two for a mid-sized app, separate from the time spent adopting any of the new 19.3-specific features afterward.
Should I adopt all four new APIs at once?
No. Treat them as independent tools rather than a package deal. A typical adoption path looks like: use Fragment Refs wherever a wrapper-div workaround is currently causing layout bugs, add <ViewTransition> around one or two high-value interactions (a reorder, a route change, a modal open), skip browser() entirely until you add a server-rendering framework, and only touch Trusted Types if your security team specifically requires it. Rolling all four into a codebase in a single pull request makes it much harder to tell which change caused a regression if something breaks in review.
Yusuf Demir
Yusuf Demir is the Cloud & Software Reporter at TrendinTech, where he covers cloud infrastructure, enterprise platforms, developer tools and the digital transformation of businesses in the UK and the United States. He previously reported on enterprise technology for The Register in London and covered the cloud and SaaS beat for TechCrunch, following the competition between AWS, Microsoft Azure and Google Cloud, the open source licensing disputes and the rise of Kubernetes. Yusuf holds an MEng in Computing from Imperial College London and speaks regularly at KubeCon and AWS re:Invent, where he moderates conversations with engineers and chief technology officers. He is most interested in the gap between vendor roadmaps and the systems engineers actually run, and in what the cloud bill looks like once the free credits expire.
All stories by Yusuf Demir (296)