An ESLint plugin to identify and fix breaking changes when upgrading React 18 to React 19
You'll first need to install ESLint:
$ npm install eslint --save-dev
Next, install eslint-plugin-react-19-upgrade:
$ npm install eslint-plugin-react-19-upgrade --save-dev
Add react-19-upgrade to the plugins section of your .eslintrc or equivalent configuration file. You can omit the eslint-plugin- prefix:
{
"plugins": ["react-19-upgrade"]
}Then configure the rules you want to use under the rules section.
{
"rules": {
"react-19-upgrade/no-default-props": "error",
"react-19-upgrade/no-prop-types": "warn",
"react-19-upgrade/no-legacy-context": "error",
"react-19-upgrade/no-string-refs": "error",
"react-19-upgrade/no-factories": "error",
"react-19-upgrade/no-legacy-react-dom": "error",
"react-19-upgrade/no-legacy-react-dom-server": "error",
"react-19-upgrade/no-legacy-test-utils": "error",
"react-19-upgrade/no-legacy-react-is": "error",
"react-19-upgrade/no-shallow-renderer": "error",
"react-19-upgrade/no-implicit-ref-callback-return": "error"
}
}no-default-props: MovedefaultPropsto default parameters in the destructured props. Fixable by ESLint.no-prop-types: Avoid usingpropTypesas they are now ignored in React 19.no-legacy-context: Disallow the use of legacy context APIs in React class components.no-string-refs: Disallow the use of string refs in React components.no-factories: Disallow module pattern factories and React.createFactory.no-legacy-react-dom: Disallow removedreact-domAPIs (render,hydrate,unmountComponentAtNode,findDOMNode, plus the removedunstable_flushControlled,unstable_createEventHandle,renderSubtreeIntoContainer/unstable_renderSubtreeIntoContainer, andunstable_runWithPriority). UsecreateRoot/hydrateRootfromreact-dom/client, or refs, instead.no-legacy-react-dom-server: DisallowrenderToNodeStreamandrenderToStaticNodeStreamfromreact-dom/server. UserenderToPipeableStreaminstead (note: different signature).no-legacy-test-utils: Disallow importing fromreact-dom/test-utils, which is removed entirely in React 19.actimports are rewritten to import fromreact(Fixable by ESLint); the other utilities (Simulate,renderIntoDocument, etc.) are reported with a pointer to@testing-library/react. Supersedesno-legacy-test-utils-act— enable one or the other, not both.no-legacy-test-utils-act: Importactfromreact, notreact-dom/test-utils. Fixable by ESLint. Narrower thanno-legacy-test-utils(only flagsact); kept for backwards compatibility.no-legacy-react-is: DisallowisConcurrentModeandisAsyncModefromreact-is, removed in React 19 without a replacement.no-shallow-renderer: Disallow importingreact-test-renderer/shallow, removed in React 19. Rewritten toreact-shallow-renderer(install it first). Fixable by ESLint.no-implicit-ref-callback-return: Disallow implicit returns in JSX ref callbacks (ref={el => (this.el = el)}) — React 19 treats the returned value as a cleanup function. The body is wrapped in braces (Fixable by ESLint). Only arrow expression bodies are flagged, matching the officialreact/19codemod; explicitreturnstatements and function-expression refs are left alone.
React 19 makes ref a regular prop, so element.ref becomes element.props.ref. This plugin does not lint for it — any sound static heuristic either misses real cases or produces false positives on valid code. Use the official React codemod instead: npx codemod@latest react/19/replace-ref-access.