codebase-doctor migration dossier

Session 7bd3ea4f · generated 2026-09-26 10:27:39 UTC

react ^17.0.2 → 18.3.1

pantry-list-redux (local) · codebase-doctor/react-18.3.1-upgrade from main

Implementing · 9 open items

  1. Analyse — done
  2. Rules — done
  3. Risk — done
  4. Plan — done
  5. Approve — done
  6. Implement — current step
  7. Verify — not started
  8. Pull request — not started
11
files use react
16 imports · 19 API usages
9
files need attention
4 high-risk
8
breaking changes apply
3 automated · 5 manual/review
not run
verification
verify_migration has not run

01Risk & priority

Which files matter most, scored from concrete API evidence.

  • High ≥70 4
  • Medium 40–69 0
  • Low 1–39 5
  • Stable 0 2
  1. 90 src/legacy/toast.jsx react-bc-1 +40, react-bc-8 +20, root bootstrap +30
  2. 90 src/legacy/toast.test.jsx react-bc-1 +40, react-bc-3 +20, react-bc-12 +10, test harness +20
  3. 80 src/index.jsx react-bc-1 +40, react-bc-6 +10, root bootstrap +30
  4. 70 src/ssr-entry.jsx react-bc-2 +40, root bootstrap +30
  5. 20 src/App.jsx react-bc-4 +20
  6. 20 src/components/AddItemForm.jsx react-bc-4 +20
  7. 20 src/components/ItemList.jsx react-bc-4 +20
  8. 20 src/components/SyncStatus.jsx react-bc-4 +20

02Breaking changes

The rules that apply to this repository — and why.

high react-bc-2 automated Not started

ReactDOM.hydrate() is deprecated in React 18 (legacy mode) — replace with hydrateRoot()

1 file: src/ssr-entry.jsx

medium react-bc-3 automated Not started

act() from react-dom/test-utils is deprecated — import act from 'react' (available from React 18.3)

1 file: src/legacy/toast.test.jsx

medium react-bc-4 manual Not started

Automatic batching: setState calls in timeouts/promises/native handlers are now batched — may affect code or tests relying on intermediate renders

How to fix Review any tests that assert on intermediate render states inside setTimeout, Promise.then, or native event handlers. Wrap those assertions in act() or use flushSync from 'react-dom' to opt out of automatic batching where needed.

4 files: src/App.jsx, src/components/AddItemForm.jsx, src/components/ItemList.jsx, src/components/SyncStatus.jsx

low react-bc-6 manual Not started

Strict Mode now double-invokes effects (mount → unmount → mount) in development to surface side effects

How to fix Audit useEffect, useLayoutEffect, and class lifecycle methods for non-idempotent side effects (network requests, analytics calls, subscriptions). Ensure cleanup functions are implemented so double-invocation does not cause observable problems in development.

1 file: src/index.jsx

low react-bc-7 manual Not started

ReactDOM.unstable_batchedUpdates is redundant under React 18 automatic batching

How to fix With React 18's automatic batching, explicit calls to unstable_batchedUpdates are no longer needed in most cases. Remove them and verify behaviour. If you need to force synchronous rendering, use flushSync from 'react-dom' instead.

1 file: src/legacy/store.js

medium react-bc-8 manual Not started

ReactDOM.unmountComponentAtNode() is deprecated in React 18 — call root.unmount() on the root returned by createRoot()

How to fix Keep the root returned by createRoot(container) and call root.unmount() instead of unmountComponentAtNode(container). In tests, unmount the root the test created (React Testing Library unmounts automatically).

1 file: src/legacy/toast.jsx

low react-bc-12 manual Not started

Tests that render with react-dom directly need globalThis.IS_REACT_ACT_ENVIRONMENT = true under React 18

How to fix In the test setup file (e.g. setupTests.js / jest.setup.js), set globalThis.IS_REACT_ACT_ENVIRONMENT = true; so React 18 knows it runs in an act-aware test environment. React Testing Library ≥ 13 sets this automatically for tests that use it.

1 file: src/legacy/toast.test.jsx

03Compatibility

Declared packages whose peer ranges could block the install.

Checked 607 packages via package-lock.json (lockfileVersion 3).

PackageRequiresSourceResolution
@testing-library/react@12.1.5react <18.0.0lockfileauto upgraded to ^14.3.1 in the dependency step
react-redux@7.2.6react ^16.8.3 || ^17lockfilemanual try ^7.2.7

04Migration plan

What will change, in order, and what has happened so far.

Plan 70abe5689cf4 approved 2026-09-26 10:26:11 UTC. Approval is bound to this exact plan — a changed plan needs a new approval.

Estimated effort HIGH · 11 steps · 1 applied automatically · 1 completed manually

  1. Resolve peer-dependency conflicts with react 18.3.1
    Completed manually manual human 1 file commit 7d55c52

    Completed manually: react-redux 7.2.6 → ^7.2.7 (peer range includes React 18)

  2. Update react, react-dom, @testing-library/react to 18.3.1 and install
    Applied automatically config automated 2 files commit dd04a69 attempt 2
    • react ^17.0.2 → ^18.3.1
    • react-dom ^17.0.2 → ^18.3.1 (companion)
    • @testing-library/react ^12.1.5 → ^14.3.1 (peer-compat)

    Ran npm install --no-audit --no-fund; installed react@18.3.1, react-dom@18.3.1.

  3. ReactDOM.render() is deprecated in React 18 (the app keeps running in legacy React 17 mode) — replace with createRoot().render()
    Not started codemod automated 3 files
  4. ReactDOM.hydrate() is deprecated in React 18 (legacy mode) — replace with hydrateRoot()
    Not started codemod automated 1 file
  5. act() from react-dom/test-utils is deprecated — import act from 'react' (available from React 18.3)
    Not started codemod automated 1 file
  6. Automatic batching: setState calls in timeouts/promises/native handlers are now batched — may affect code or tests relying on intermediate renders
    Not started manual human 4 files
  7. ReactDOM.unmountComponentAtNode() is deprecated in React 18 — call root.unmount() on the root returned by createRoot()
    Not started manual human 1 file
  8. Strict Mode now double-invokes effects (mount → unmount → mount) in development to surface side effects
    Not started manual human 1 file
  9. ReactDOM.unstable_batchedUpdates is redundant under React 18 automatic batching
    Not started manual human 1 file
  10. Tests that render with react-dom directly need globalThis.IS_REACT_ACT_ENVIRONMENT = true under React 18
    Not started manual human 1 file
  11. Update and verify tests
    Not started test human 3 files

05Affected files

Every file that uses the package family, with line-level evidence.

11 files

src/legacy/toast.jsx react-bc-1react-bc-8 90
LineUsageSourceRules
1import reactimport React from 'react';—
2import react-domimport ReactDOM from 'react-dom';—
17render · react-domReactDOM.render(<Toast message={message} />, host);react-bc-1
19unmountComponentAtNode · react-domReactDOM.unmountComponentAtNode(host);react-bc-8
src/legacy/toast.test.jsx test react-bc-3react-bc-12react-bc-1 90
LineUsageSourceRules
1import reactimport React from 'react';—
2import react-domimport ReactDOM from 'react-dom';—
3import react-dom/test-utilsimport { act } from 'react-dom/test-utils';—
3act · react-dom/test-utilsimport { act } from 'react-dom/test-utils';react-bc-3 react-bc-12
13act · react-dom/test-utilsact(() => {react-bc-3 react-bc-12
18act · react-dom/test-utilsact(() => {react-bc-3 react-bc-12
27act · react-dom/test-utilsact(() => {react-bc-3 react-bc-12
28render · react-domReactDOM.render(<p>host</p>, container);react-bc-1 react-bc-12
src/index.jsx react-bc-1react-bc-6 80
LineUsageSourceRules
1import reactimport React from 'react';—
2import react-domimport ReactDOM from 'react-dom';—
5render · react-domReactDOM.render(react-bc-1
6StrictMode · react<React.StrictMode>react-bc-6
src/ssr-entry.jsx react-bc-2 70
LineUsageSourceRules
1import reactimport React from 'react';—
2import react-domimport ReactDOM from 'react-dom';—
6hydrate · react-domReactDOM.hydrate(<App />, document.getElementById('root'));react-bc-2
src/App.jsx react-bc-4 20
LineUsageSourceRules
1import reactimport React, { useReducer } from 'react';—
1useReducer · reactimport React, { useReducer } from 'react';react-bc-4
23useReducer · reactconst [items, dispatch] = useReducer(itemsReducer, []);react-bc-4
src/components/AddItemForm.jsx react-bc-4 20
LineUsageSourceRules
1import reactimport React, { useState } from 'react';—
1useState · reactimport React, { useState } from 'react';react-bc-4
4useState · reactconst [name, setName] = useState('');react-bc-4
src/components/ItemList.jsx react-bc-4 20
LineUsageSourceRules
1import reactimport React from 'react';—
3Component · reactexport default class ItemList extends React.Component {react-bc-4
src/components/SyncStatus.jsx react-bc-4 20
LineUsageSourceRules
1import reactimport React, { useState } from 'react';—
1useState · reactimport React, { useState } from 'react';react-bc-4
7useState · reactconst [status, setStatus] = useState('idle');react-bc-4
8useState · reactconst [syncedAt, setSyncedAt] = useState(null);react-bc-4
src/legacy/store.js react-bc-7 10
LineUsageSourceRules
1import react-domimport ReactDOM from 'react-dom';—
13unstable_batchedUpdates · react-domReactDOM.unstable_batchedUpdates(() => {react-bc-7
src/App.test.jsx test 0
LineUsageSourceRules
1import reactimport React from 'react';—
src/components/SyncStatus.test.jsx test 0
LineUsageSourceRules
1import reactimport React from 'react';—

06Changes

Each step's commit on the migration branch.

2 files changed on codebase-doctor/react-18.3.1-upgrade since main.

Resolve peer-dependency conflicts with react 18.3.17d55c52
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.jsonindex 122ca99..1a3dbfc 100644--- a/package.json+++ b/package.json@@ -12,7 +12,7 @@   "dependencies": {     "react": "^17.0.2",     "react-dom": "^17.0.2",-    "react-redux": "7.2.6",+    "react-redux": "^7.2.7",     "redux": "^4.2.1"   },   "devDependencies": {
Update react, react-dom, @testing-library/react to 18.3.1 and installdd04a69
 package-lock.json | 90 +++++++++++++++++++++++++------------------------------
 package.json      |  6 ++--
 2 files changed, 43 insertions(+), 53 deletions(-)
diff --git a/package-lock.json b/package-lock.json@@ lockfile diff omitted: +40 −50 lines @@diff --git a/package.json b/package.jsonindex 1a3dbfc..6a65b85 100644--- a/package.json+++ b/package.json@@ -10,8 +10,8 @@     "lint": "eslint src"   },   "dependencies": {-    "react": "^17.0.2",-    "react-dom": "^17.0.2",+    "react": "^18.3.1",+    "react-dom": "^18.3.1",     "react-redux": "^7.2.7",     "redux": "^4.2.1"   },@@ -19,7 +19,7 @@     "@babel/core": "^7.24.0",     "@babel/preset-env": "^7.24.0",     "@babel/preset-react": "^7.24.0",-    "@testing-library/react": "^12.1.5",+    "@testing-library/react": "^14.3.1",     "babel-jest": "^29.7.0",     "esbuild": "^0.23.0",     "eslint": "^9.9.0",

07Verification

Does the upgraded code pass the repository's own checks?

Verification has not run yet. It runs after the plan is approved and applied: verify_migration checks the installed versions, then runs the repository's own lint, test and build scripts.

08Remaining work

What still needs a human before (or after) merging.

  • Not startedstep-3-react-bc-1

    ReactDOM.render() is deprecated in React 18 (the app keeps running in legacy React 17 mode) — replace with createRoot().render()

    Run this step.

  • Not startedstep-4-react-bc-2

    ReactDOM.hydrate() is deprecated in React 18 (legacy mode) — replace with hydrateRoot()

    Run this step.

  • Not startedstep-5-react-bc-3

    act() from react-dom/test-utils is deprecated — import act from 'react' (available from React 18.3)

    Run this step.

  • Not startedstep-6-react-bc-4

    Automatic batching: setState calls in timeouts/promises/native handlers are now batched — may affect code or tests relying on intermediate renders

    Review any tests that assert on intermediate render states inside setTimeout, Promise.then, or native event handlers. Wrap those assertions in act() or use flushSync from 'react-dom' to opt out of automatic batching where needed.

  • Not startedstep-7-react-bc-8

    ReactDOM.unmountComponentAtNode() is deprecated in React 18 — call root.unmount() on the root returned by createRoot()

    Keep the root returned by createRoot(container) and call root.unmount() instead of unmountComponentAtNode(container). In tests, unmount the root the test created (React Testing Library unmounts automatically).

  • Not startedstep-8-react-bc-6

    Strict Mode now double-invokes effects (mount → unmount → mount) in development to surface side effects

    Audit useEffect, useLayoutEffect, and class lifecycle methods for non-idempotent side effects (network requests, analytics calls, subscriptions). Ensure cleanup functions are implemented so double-invocation does not cause observable problems in development.

  • Not startedstep-9-react-bc-7

    ReactDOM.unstable_batchedUpdates is redundant under React 18 automatic batching

    With React 18's automatic batching, explicit calls to unstable_batchedUpdates are no longer needed in most cases. Remove them and verify behaviour. If you need to force synchronous rendering, use flushSync from 'react-dom' instead.

  • Not startedstep-10-react-bc-12

    Tests that render with react-dom directly need globalThis.IS_REACT_ACT_ENVIRONMENT = true under React 18

    In the test setup file (e.g. setupTests.js / jest.setup.js), set globalThis.IS_REACT_ACT_ENVIRONMENT = true; so React 18 knows it runs in an act-aware test environment. React Testing Library ≥ 13 sets this automatically for tests that use it.

  • Not startedstep-11-tests

    Update and verify tests

    Update any test utilities that changed in the new version, then confirm the full suite passes with verify_migration. This step makes no automated source changes.

09Metrics

Measured values, labelled estimates, and what is not measured.

measured

Files using react11
Files with breaking-change evidence9
Files changed on branch2
Steps applied automatically1 / 11
Steps completed manually1
Steps still open (manual / skipped / failed)0
Elapsed session time (wall-clock)2 min

estimate

Manual effort
~4.5 h9 affected files × 30 min/file (heuristic, not benchmarked)
Time saved
~4.5 hestimated manual effort − measured elapsed time

not measured

  • Bobcoin consumption — not measured (not exposed to the MCP server)
  • Accuracy — not measured (no ground-truth benchmark)

10Repository

How the repository was analysed.

Repositorypantry-list-redux (local)
Default branchmain
Migration branchcodebase-doctor/react-18.3.1-upgrade
Languagejavascript
Package managernpm
Test frameworkjest
Lint / test / buildnpm run lint · npm run test · npm run build
Source files scanned13
Rule setreact-17-to-18.json
Session7bd3ea4f-0270-45ff-ab92-81d86606b3d4
Local repository without a GitHub remote — every step works, but create_pull_request is unavailable.