the complete checklist
Whoever stayed on React 17 has several major versions and deep changes to rendering, the root API and concurrent features ahead of them. The React 17 to 19 upgrade only succeeds safely with an intermediate step through React 18 and a structured checklist, not a single risky jump.
Table of Contents
- 1. Why the React 17 to 19 upgrade doesn't work in one step
- 2. Stage one: React 17 to React 18
- 3. createRoot instead of ReactDOM.render
- 4. Understanding and fixing StrictMode double invocations
- 5. Stage two: React 18 to React 19
- 6. Removed APIs: PropTypes, defaultProps and string refs
- 7. Hydration errors and Suspense behaviour
- 8. Checking third party libraries for compatibility
- 9. Checklist compared directly across versions
- 10. Summary
- 11. FAQ
1. Why the React 17 to 19 upgrade doesn't work in one step
A React 17 to 19 upgrade technically skips two major versions, each with its own breaking changes. React 18 introduced the new root API, automatic batching and concurrent features, while React 19 additionally removed several APIs that had been marked deprecated for years. Whoever tries to jump straight from 17 to 19 has to debug both sets of changes at once, which makes it nearly impossible to attribute errors to a single cause.
The recommended strategy for the React 17 to 19 upgrade is therefore always two staged: first update to React 18, fix every warning and error in that version, stabilize the build, and only then take the second step to React 19. This order makes every error message attributable to a single, clearly bounded cause.
A second reason for the two staged approach: many third party libraries added React 18 compatibility long ago, while React 19 support is still missing for some packages. An intermediate stop at React 18 gives the ecosystem time, while your own codebase already benefits in parallel from automatic batching and the new concurrent features.
2. Stage one: React 17 to React 18
The first part of the React 17 to 19 upgrade focuses entirely on React 18. Installation itself is straightforward, but the code needs adjustments in several places afterward before the new version actually runs error free. The most important change concerns the root API, covered in detail in the next section.
# Step 1 of the React 17 to 19 upgrade: install React 18 first
npm install react@18 react-dom@18
# Check for peer dependency warnings from third-party libraries
npm ls react
# Run the full test suite before touching any application code
npm test -- --watchAll=false
3. createRoot instead of ReactDOM.render
Since React 18, ReactDOM.render is deprecated and gets replaced by createRoot from react-dom/client. This change is mandatory to gain access to concurrent features such as automatic batching at all. Without this step, the application technically keeps running in the React 17 compatible legacy mode, which leaves the actual React 17 to 19 upgrade incomplete even if the package version has already been bumped.
// BEFORE: React 17 legacy render API
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
// AFTER: React 18+ createRoot API, required for concurrent features
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
4. Understanding and fixing StrictMode double invocations
Since React 18, StrictMode runs mount, unmount and remount for every component in development, to surface effects that don't clean up properly. For the React 17 to 19 upgrade this means: effects that previously ran only once now run twice, which leads to duplicate subscriptions, duplicate API calls or duplicate analytics events wherever cleanup is broken.
The fix is almost always the same: every useEffect that opens a resource must return a cleanup function that closes it again. Effects that already followed this pattern consistently before the upgrade show no visible behaviour under StrictMode at all, because mount and unmount cancel each other out exactly. Whoever sees duplicate network calls in the browser devtools after the upgrade has almost always found a missing cleanup handler, not an actual bug in StrictMode itself.
5. Stage two: React 18 to React 19
Once React 18 runs stably, the second part of the React 17 to 19 upgrade follows. React 19 itself does not further fundamentally change the rendering model, but fully removes several APIs that were only marked deprecated in React 18, and introduces new primitives such as actions and the use hook.
# Step 2 of the React 17 to 19 upgrade: install React 19
npm install react@19 react-dom@19
# React 19 ships an official codemod for common breaking changes
npx codemod@latest react/19/migration-recipe
# Re-run the test suite and check for new console warnings
npm test -- --watchAll=false
6. Removed APIs: PropTypes, defaultProps and string refs
The React 19 upgrade removes PropTypes support from the React core package entirely, defaultProps for function components gets ignored, and string refs like ref="myInput" no longer work. For the React 17 to 19 upgrade, this is the section with the most necessary code changes, because many older codebases use exactly these three patterns extensively.
// BEFORE: PropTypes and defaultProps, both removed in React 19 for function components
import PropTypes from 'prop-types';
function Greeting({ name }) {
return <p>Hello, {name}</p>;
}
Greeting.propTypes = { name: PropTypes.string };
Greeting.defaultProps = { name: 'Guest' };
// AFTER: TypeScript types plus a default parameter, no runtime dependency needed
type GreetingProps = { name?: string };
function Greeting({ name = 'Guest' }: GreetingProps) {
return <p>Hello, {name}</p>;
}
String refs were already marked unsafe long ago, because they don't play well with several concurrent renders. The replacement is always useRef in function components or createRef in class components, combined with a direct ref={myRef} attribute instead of a string literal.
7. Hydration errors and Suspense behaviour
React 19 noticeably sharpens error messages for hydration mismatches between server and client, which in the context of the React 17 to 19 upgrade often surfaces problems that were previously silently ignored, such as different date formatting between server and client time zone. These errors are not a new problem, they were simply not reported with the same clarity before.
Suspense boundaries also behave more consistently in React 19 when streaming server components, which affects applications already using React Server Components. For classic client only applications without server components, this part of the upgrade is usually unremarkable and requires no code changes.
8. Checking third party libraries for compatibility
Before each of the two upgrade steps, the peerDependencies declaration of every library in use should be checked. State management libraries, UI component libraries and test utilities often declare explicit version ranges for React, and an upgrade without prior checking frequently leads to npm install errors from peer dependency conflicts.
A pragmatic approach for the React 17 to 19 upgrade is to set up a test project with the same dependencies and check compatibility there first, before touching the production codebase. Libraries without active maintenance and without React 18 or 19 support are a good reason to consider a library swap at the same time, instead of letting the upgrade fail on a single outdated dependency.
9. Checklist compared directly across versions
The table below maps the most important points of the React 17 to 19 upgrade to the version in which they become relevant.
| Change | React 17 | React 18 | React 19 |
|---|---|---|---|
| Root API | ReactDOM.render | createRoot mandatory | Unchanged |
| Automatic batching | Only in React events | Everywhere | Unchanged |
| PropTypes | Supported | Deprecated | Removed |
| String refs | Supported | Deprecated | Removed |
| Actions and use hook | Not available | Not available | Newly introduced |
The table makes it clear why the React 17 to 19 upgrade should be treated as two separate undertakings. Each row concerns a different version, and whoever mixes both steps can no longer clearly attribute error messages to a cause.
Mironsoft
React upgrades, version migrations and compatibility audits
Still on React 17, but new features need React 19?
We run the React 17 to 19 upgrade in controlled stages, check third party libraries beforehand, and secure every step with tests.
Compatibility Audit
Checking all dependencies for React 18 and 19 support
Guided Upgrade
Two controlled stages instead of a risky direct jump
Codemod Application
Automated migration of removed APIs with subsequent review
10. Summary
The React 17 to 19 upgrade should never be planned as a single step. Stage one moves to React 18, replaces ReactDOM.render with createRoot, and fixes every StrictMode double invocation through correct effect cleanup. Stage two moves to React 19, permanently removes PropTypes, defaultProps for function components and string refs, and brings new primitives such as actions.
The success of the React 17 to 19 upgrade depends heavily on how thoroughly third party libraries are checked for compatibility beforehand. A test project with the same dependencies surfaces peer dependency conflicts before they block the production codebase, turning a risky jump into a plannable, two staged undertaking.
React 17 to 19 Upgrade Checklist: The Essentials
Intermediate step React 18
createRoot instead of ReactDOM.render, secure StrictMode double invocations with cleanup functions.
Removed APIs in React 19
PropTypes, defaultProps for function components and string refs fully removed.
Third party check
Check peerDependencies of all libraries before each of the two steps.
Use codemods
The official React 19 codemod package automates many mechanical changes.