Skip to the chapter
←ReactGGroundwork⚛React
React/Advanced/A18
51 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛A18 · Part 3 · Advanced

Migrating and upgrading

Nobody rewrites a working application. The skill is moving it while it keeps shipping.

5 min read8 sections2 exercises
51 of 57

In this chapter

  • The question you will actually be asked
  • The order that works
  • Version by version, what actually breaks
  • Class to function, honestly
  • Redux to a query cache

and 3 more

about 5 min
08On this page
  1. The question you will actually be asked
  2. The order that works
  3. Version by version, what actually breaks
  4. Class to function, honestly
  5. Redux to a query cache
  6. Adopting Server Components without stopping
  7. Measuring that it is working
  8. What to leave alone

The question you will actually be asked

"We have a four-year-old React app. Two hundred thousand lines, class components in the older half, Redux holding server data, on React 17. How do you modernise it?" The wrong answer is a rewrite. The right answer is an order, a way to measure progress, and a story for the parts you will never touch.

Why the rewrite loses

A rewrite competes with a codebase that is already correct about a thousand edge cases nobody wrote down, while the original keeps moving. You ship nothing for months, then ship something worse. The strangler pattern — new code in the new style, old code migrated when it is touched anyway — keeps the application shippable the whole way.

The order that works

  1. Get on a supported version first. Everything else is easier from React 18+, and the upgrade itself is usually small.
  2. Take server state out of Redux. Usually the single biggest win: a query library deletes thousands of lines of actions, reducers and loading flags, and fixes caching you were never going to write.
  3. Adopt TypeScript file by file, with allowJs on. Never as a big-bang conversion.
  4. Convert classes only when you are already in the file. A working class component is not a bug.
  5. Then consider the framework move, if there is a real reason for it.

Version by version, what actually breaks

UpgradeBreaks
16 → 17Almost nothing. Event delegation moved from document to the root container, which breaks code mixing React and native listeners.
17 → 18ReactDOM.render → createRoot. Automatic batching now applies in timeouts and promises, so code relying on separate renders changes behaviour. Strict Mode double-invokes effects.
18 → 19propTypes and defaultProps removed for function components. String refs gone. forwardRef no longer needed. New JSX transform required.
19.x → 19.2.4+Nothing breaks — it is a security floor. Any app using Server Components or Server Functions must be at 19.0.4, 19.1.5 or 19.2.4 or later for React2Shell and the follow-up CVEs, and on a framework release that bundles those fixes.
19.2 → 19.3No breaking changes listed, but two behaviour changes can surface old bugs: Strict Mode now double invokes effects during hydration, so a non-idempotent effect in an SSR app shows up; and transitions render independently, so code that assumed two transitions commit together may not.
npx codemod@latest react/19/migration-recipe     // does most of the mechanical work

Run the codemods, read the diff, and do not trust them blindly — they are excellent at renames and poor at judgement.

Class to function, honestly

ClassFunction
this.state, setStateuseState, or useReducer for related fields
componentDidMountuseEffect(fn, [])
componentDidUpdateuseEffect(fn, [deps])
componentWillUnmountthe cleanup return
shouldComponentUpdatememo
getDerivedStateFromErrorstill a class — error boundaries have no hook

The table is the easy half. The trap is that componentDidUpdate often held logic comparing previous and next props, and the mechanical translation is an effect that runs at the wrong times. Ask what the code is synchronising with rather than transliterating the lifecycle — frequently the answer is that it should not be an effect at all.

Redux to a query cache

// before: 4 files, ~120 lines, for one endpoint
actions/users.js  reducers/users.js  selectors/users.js  sagas/users.js

// after
const { data } = useQuery({ queryKey: ["users"], queryFn: getUsers });

Do it one slice at a time, keeping both alive. The store stays for genuine client state — and once the server slices are gone, there is often so little left that the store itself can go too.

Adopting Server Components without stopping

  1. Move to the framework's new router with everything marked "use client". Nothing changes behaviourally; you now have the door.
  2. Push the directive down the tree, one layout at a time. Each push turns a subtree into server-rendered output.
  3. Move data fetching from effects into the server components that now exist above them.
  4. Delete the API routes that existed only so the browser could reach the database.

Each step is shippable on its own and reversible. Measure the client bundle after each one — if it is not falling, the directive is not moving.

Measuring that it is working

MetricTells you
Files with "use client"How much is still client-side, as one number
Class components remainingProgress, and it should only fall
Client bundle sizeWhether the migration is buying anything
TypeScript coverage, or files still .jsThe typed fraction

Put these in CI. A migration without a number nobody can see is a migration that stalls quietly six weeks in, and the only person who notices is the one who started it.

What to leave alone

Code that works, is not touched, and nobody is asking about. An admin screen three people use, written in classes, with no bugs, is not technical debt — it is finished software. Migrating it costs real time and buys nothing. Say that out loud in the interview; it is the answer that separates someone who has run a migration from someone who has read about one.

Interview answer, one sentence

"Strangler, not rewrite: get onto a supported version, move server state out of Redux into a query cache — usually the biggest single win — adopt TypeScript file by file, and convert classes only when you are already in the file. Put the progress numbers in CI, and leave alone the code that works and nobody touches."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 4 testsA legacy class component with a lifecycle bugadvanced
  • 5 testscomponentDidUpdate needs its own guardadvanced
All 54 for this level →
A18

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. A17Previous · A17Custom renderers
  2. A18you are here
  3. Next · A19Debugging in productionThe bug is on someone else's device, on a network you cannot reproduce.4 min read

01On this page

8 sections · 0%

  1. 01The question you will actually be asked
  2. 02The order that works
  3. 03Version by version, what actually breaks
  4. 04Class to function, honestly
  5. 05Redux to a query cache
  6. 06Adopting Server Components without stopping
  7. 07Measuring that it is working
  8. 08What to leave alone

[] chapters · t top