codebase-doctor migration dossier

Session 3fd51510 · generated 2026-09-26 10:19:05 UTC

react ^17.0.2 → 18.3.1

codebase-doctor-sandbox/pantry-list · codebase-doctor/react-18.3.1-upgrade from main

Awaiting approval

  1. Analyse — done
  2. Rules — done
  3. Risk — done
  4. Plan — done
  5. Approve — current step
  6. Implement — not started
  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 confirmed by docs 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 confirmed by docs 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 confirmed by docs 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 confirmed by docs 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 confirmed by docs 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 602 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

04Migration plan

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

Plan b3b654d66018 is awaiting approval. The backend refuses to create the branch or change any file until you reply “approved”.

Estimated effort HIGH · 10 steps · 0 applied automatically · 0 completed manually

  1. Update react, react-dom, @testing-library/react to 18.3.1 and install
    Not started config automated 2 files
    • 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)
  2. 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
  3. ReactDOM.hydrate() is deprecated in React 18 (legacy mode) — replace with hydrateRoot()
    Not started codemod automated 1 file
  4. act() from react-dom/test-utils is deprecated — import act from 'react' (available from React 18.3)
    Not started codemod automated 1 file
  5. 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
  6. ReactDOM.unmountComponentAtNode() is deprecated in React 18 — call root.unmount() on the root returned by createRoot()
    Not started manual human 1 file
  7. Strict Mode now double-invokes effects (mount → unmount → mount) in development to surface side effects
    Not started manual human 1 file
  8. ReactDOM.unstable_batchedUpdates is redundant under React 18 automatic batching
    Not started manual human 1 file
  9. Tests that render with react-dom directly need globalThis.IS_REACT_ACT_ENVIRONMENT = true under React 18
    Not started manual human 1 file
  10. 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.

Nothing has been changed — the plan is not approved, and the backend refuses to modify the repository until it is.

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-1-dependencies

    Update react, react-dom, @testing-library/react to 18.3.1 and install

    Run this step.

  • Not startedstep-2-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-3-react-bc-2

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

    Run this step.

  • Not startedstep-4-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-5-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-6-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-7-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-8-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-9-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-10-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 branchbranch not created
Steps applied automatically0 / 10
Steps completed manually0
Steps still open (manual / skipped / failed)0
Elapsed session time (wall-clock)0 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.

Repositorycodebase-doctor-sandbox/pantry-list
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 + your migration docs
Session3fd51510-5cd8-41f8-8271-285c542bb72f