Skip to content

React 19: upgrade standalone Calypso build - final bump#112095

Merged
paulopmt1 merged 12 commits into
trunkfrom
update/react-19
Jul 6, 2026
Merged

React 19: upgrade standalone Calypso build - final bump#112095
paulopmt1 merged 12 commits into
trunkfrom
update/react-19

Conversation

@paulopmt1

@paulopmt1 paulopmt1 commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Fixes DOTCOM-17552

Proposed Changes

Upgrade the standalone Calypso build to React 19.2.7. This is a clean re-split of the long-running spike branch (#111325) into three reviewable commits, rebased onto current trunk:

  1. Bump dependencies and patch @wordpress/elementreact/react-dom → 19.2.7 (with matching resolutions), @wordpress/element → 8.0.0, and a Yarn patch for @wordpress/element's react-platform.
  2. Update TypeScript and Jest config — global JSX namespace shim, client/tsconfig.json, the shared TS config package, and the apps Jest preset.
  3. Fix type and ref errors across client and packages — ref-receiver widening, redundant JSDoc/prop-type removal, optional-prop fixes, and a theme snapshot update.

About the @wordpress/element patch

React 19 removed findDOMNode and moved render/hydrate/unmountComponentAtNode out of react-dom, but @wordpress/element@8.0.0 still imports all four by name from react-dom, so the named ESM imports fail to resolve at build time. The patch reimplements render/hydrate/unmountComponentAtNode on top of createRoot/hydrateRoot and makes findDOMNode throw, keeping the package's export surface intact.

A Yarn patch is the right mechanism here: no published @wordpress/element (through 8.1.0) ships a React-19-compatible build, and the patch fixes the module once at the source for every toolchain (webpack, Jest, node), versus per-tool aliasing. It can be removed as soon as a React-19-compatible @wordpress/element is released — bump the dependency and drop the patch entry.

Why are these changes being made?

Part of the React 19 migration (DOTCOM-17400). This re-split replaces the spike branch's 23-commit history with a clean, reviewable sequence on top of current trunk.

Testing Instructions

  • Check that Yarn install works as expected and local calypso as well:

    image
  • CI type_check_client should pass.

  • Build and smoke-test the standalone Calypso client; verify no React/TypeScript console errors.

Pre-merge Checklist

  • Has the general commit checklist been followed? (PCYsg-hS-p2)
  • Have you written new tests for your changes?
  • Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
  • Have you checked for TypeScript, React or other console errors?
  • For UI changes, have you tested the affected components in dark mode?
  • Have you tested accessibility for your changes?
  • Have you used memoizing on expensive computations?
  • Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    • For UI changes, have we tested the change in various languages?
  • For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?

🤖 Generated with Claude Code

paulopmt1 and others added 3 commits June 29, 2026 17:32
Move react and react-dom to 19.2.7 (with matching resolutions) and bump
@wordpress/element to 8.0.0.

@wordpress/element 8.0.0 still imports findDOMNode/render/hydrate/
unmountComponentAtNode from react-dom, all of which React 19 removed, so
add a Yarn patch that reimplements render/hydrate/unmountComponentAtNode
on top of createRoot/hydrateRoot and throws from findDOMNode. No published
@wordpress/element (through 8.1.0) ships a React-19-compatible build yet;
the patch can be dropped once one does.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the global JSX namespace shim (client/global-jsx.d.ts) and adjust
client tsconfig, the shared TypeScript config package, and the apps Jest
preset to compile cleanly against React 19's revised type definitions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve type_check_client failures under React 19's stricter typings:
widen ref receivers, drop redundant JSDoc prop-type annotations and unused
JSX type imports, make optional props optional, and update the theme test
snapshot. Touches client/components, layout, my-sites, me, site-profiler,
dashboard, reader, and the components / wpcom-template-parts packages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
render,
unmountComponentAtNode
};
-//# sourceMappingURL=react-platform.mjs.map

@paulopmt1 paulopmt1 Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this file, but here's why this patch exists:

This repo is now pinned to React 19.2.7 (react + react-dom, including resolutions). @wordpress/element@8.0.0's react-platform module does named imports of findDOMNode, render, hydrate, and unmountComponentAtNode from react-dom — all of which React 19 removed (findDOMNode is gone; the other three moved to react-dom/client as createRoot/hydrateRoot).

Under React 19 those bindings resolve to undefined, and the ESM build's named imports trigger webpack export not found errors at build time.

The patch shims them: a WeakMap-backed render/hydrate/unmountComponentAtNode implemented on top of createRoot/hydrateRoot, plus a throwing findDOMNode. This keeps @wordpress/element's export surface intact for the many downstream consumers that import from it.

Why a Yarn patch (vs. alternatives)

  • Bumping the dep doesn't help — 8.0.1 and 8.1.0 (latest) ship the identical broken react-platform.mjs. There is no fixed upstream version to move to yet.
    webpack alias / NormalModuleReplacementPlugin would only fix webpack; the same broken import still has to resolve under Jest, node/ESM, Storybook, etc. The patch fixes it once at the source for every toolchain.
  • Aliasing react-dom to a shim is more invasive — it would pollute react-dom globally and could mask other React-19 migration issues we want to surface.
  • The patch is declarative, version-pinned (package.json + yarn.lock), surgical (touches only the two react-platform build files), applied deterministically at install, and regenerable via yarn patch. It's the idiomatic Yarn Berry mechanism for exactly this situation.

When can we remove it?

As soon as WordPress ships a React-19-compatible @wordpress/element (i.e. a release whose react-platform no longer imports the removed APIs from react-dom). At that point: bump the dependency and delete this patch entry. Worth re-checking on each @wordpress/element upgrade.


import './style.scss';

/**

@paulopmt1 paulopmt1 Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these JSDoc @param {{ ... }} props blocks weren't documentation in a .jsx file, they're the only mechanism to mark destructured props (className, id, status, context, …) as optional in react 19. The alternative would be moving them to a .d file, which doesn't seem better.

@paulopmt1
paulopmt1 marked this pull request as ready for review June 29, 2026 20:35
@paulopmt1
paulopmt1 requested review from a team as code owners June 29, 2026 20:35
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jun 29, 2026
Comment thread .yarnrc.yml Outdated
"strict": true,
"checkJs": false,
"isolatedModules": true,
"skipLibCheck": true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[follow-up] Issue: Adding skipLibCheck: true to the shared js-package.json config affects every package that extends it — it suppresses type checking on all .d.ts files project-wide, not just the React 19-related declaration-emit issues.

This could mask real type errors in package declarations or dependencies. The client/tsconfig.json changes in this PR (setting composite: false, declaration: false) already address the specific React 19 declaration-emit problem for the client. Is skipLibCheck also needed here, or is it papering over additional errors that should be fixed at the source?

Suggestion: If this is needed to unblock the migration, consider adding a comment noting it should be revisited/reverted once the React 19 migration completes — or scope it to a narrower config (e.g., only the configs that hit declaration-emit errors) rather than the shared base.

Source: https://github.com/Automattic/wp-calypso/blob/trunk/AGENTS.md

If it fails, fix the type errors at the source — do not silence them with // @ts-expect-error, // @ts-ignore, or as any unless you can justify it in the PR description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. A few points:

  • It's a parity fix, not a new policy. The sibling base configs already skip lib checks: ts-package.json has "skipLibCheck": true, and mixed-package.json (which client/ extends) inherits it. js-package.json was the lone outlier, so .d.ts checking was already effectively off everywhere except the JS base. This just makes it consistent.

  • skipLibCheck only skips .d.ts files, not our own source. It doesn't suppress errors in any .ts/.tsx we write — only declaration files (third-party @types and the declarations emitted by project references). So it's narrower than // @ts-ignore/as any, which silence errors in source.

  • The trigger is a generated/third-party declaration we don't own. Under the React 19 dependency bumps, the emitted .d.ts for @automattic/api-queries references @tanstack DataTag symbols that fail lib-check. There's no "source" on our side to fix — it would require upstream changes to the TanStack/api-queries type emit. The AGENTS.md line quoted targets @ts-ignore/as any in our own code; this is a different situation.

Given the other base configs already enable it, scoping it per-package would actually increase the inconsistency rather than reduce it. I'm happy to add an inline comment next to the option noting the api-queries/DataTag trigger so the rationale is discoverable for future readers.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the exact issues that you encountered, but it does seem like a worsening to enable skipLibCheck. In parallel it'd be good to drive whatever upstream improvements need to be done if that's where the issues lie, or perhaps patching them directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With skipLibCheck off, type_check_packages produces 5 errors, all inside @wordpress/element's bundled .d.ts, so until it supports React 19, we'll have two options:

  • Keep as is and remove once we can update @wordpress/element to React 19
  • Remove it and add another patch to wordpress/element which I'm exploring here

The second option seems more contained, though it's another patch. How do you feel about it, @aduth?

@github-actions

Copy link
Copy Markdown

Looks like one of the E2E tests has failed.

You can fix them following these steps:

  1. Check out this branch locally:
    gh pr checkout 112095
  2. Start Claude Code in the repo:
    claude
  3. Run the /fix-e2e-tests skill, passing this PR number:
    /fix-e2e-tests 112095
    

Replace the blanket YN0060/YN0002 log filters with message patterns
targeting the i18n-calypso React peer mismatch and the missing
@testing-library/dom peer from viewport-react and i18n-calypso.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
paulopmt1 and others added 4 commits June 30, 2026 09:03
Each case renders the full checkout tree and drives it through many
userEvent interactions, every one forcing a re-render. React 19's slower
dev-mode renderer pushes the heaviest cases (VAT cases ~4s locally) past
the previous 12s per-test cap on slower CI agents, where they time out.
Raise the per-test timeout to 30s for headroom; the suite still passes
locally with all 19 cases green.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…112133)

Patch @wordpress/block-editor's iframe so its srcDoc keeps an empty
<body> instead of self-removing it via document.currentScript.parentElement.remove().

Under React 19, <html>/<head>/<body> are host singletons that React
*adopts* from the owner document rather than creating. With the iframe's
placeholder body removed, resolveSingletonInstance found document.body
null and threw "React expected a <body> element (document.body) to exist"
every time a Gutenberg iframe mounted — e.g. the theme sheet
style-variation previews on /theme/:slug.

Keeping an empty <body> lets React 19 adopt it, apply the class/ref, and
mount the portal children into it. The existing get-compatibility-styles
export hunk is preserved in the same patch.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
},
"react-native": "src/index",
"wpScript": true,
diff --git a/build-module/components/iframe/index.mjs b/build-module/components/iframe/index.mjs

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix comes from this PR. We can only remove it after wordpress-block-editor has been migrated to be React 19 compatible.

jest.mock( 'calypso/state/products-list/selectors/is-marketplace-product' );
jest.mock( 'calypso/lib/navigate' );

// These tests seem to be particularly slow (it might be because of using

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fix comes from this PR. No issues with code, turns out React 19 local dev tests are a big slower than React 18.

Comment thread client/dashboard/components/responsive-menu/index.tsx Outdated
Comment thread client/reader/mastodon/thread-tree/thread-node.tsx
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@paulopmt1 paulopmt1 changed the title React 19: upgrade standalone Calypso build React 19: upgrade standalone Calypso build - final bump Jun 30, 2026
@paulopmt1

Copy link
Copy Markdown
Contributor Author

@paulopmt1 before merging, could we split the build/package changes with the "leftover" type and ref errors and merge them separately? The idea is that we'd get a cleaner revert for the build/package changes if things go awry.

I remember trying it last week, and it didn't work out, so I tried again today @taipeicoder, and unfortunately, this is circular:

  • The React 19 bump isn't green without the type/ref fixes (React 19 surfaces the errors).
  • The type/ref fixes aren't green without the React 19 bump (React 18 rejects them).

The initial attempt on this PR had ~35 files and ~250 lines of code, which looked promising, but the linter failed, and to make them happy, we reduced it to only ~53 line changes, which still didn't make the unit tests pass.

@taipeicoder

Copy link
Copy Markdown
Contributor

@paulopmt1 before merging, could we split the build/package changes with the "leftover" type and ref errors and merge them separately? The idea is that we'd get a cleaner revert for the build/package changes if things go awry.

I remember trying it last week, and it didn't work out, so I tried again today @taipeicoder, and unfortunately, this is circular:

  • The React 19 bump isn't green without the type/ref fixes (React 19 surfaces the errors).
  • The type/ref fixes aren't green without the React 19 bump (React 18 rejects them).

The initial attempt on this PR had ~35 files and ~250 lines of code, which looked promising, but the linter failed, and to make them happy, we reduced it to only ~53 line changes, which still didn't make the unit tests pass.

Ah, that's unfortunate. Thanks for checking.

@taipeicoder

Copy link
Copy Markdown
Contributor

Running an AI adversarial review resulted in the following findings:

  • ReactDOM.render is still being used here:
  • apps/odyssey-stats/tsconfig.json references ../../client/tsconfig-reference.json, but this PR makes that referenced config inherit composite: false from client/tsconfig.json. It's worth confirming if this change breaks the Odyssey Stats typechecking. Referenced projects are meant to have setting "composite": true. (https://www.typescriptlang.org/docs/handbook/project-references.html#composite)
  • As to the suppression @testing-library/react no longer bundles its @testing-library/dom peer, which viewport-react and i18n-calypso don't provide. Is the right fix to add @testing-library/dom as devDependencies to i18n-calypso and viewport-react? In that case, might make sense to have a follow-up PR.

@paulopmt1

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request and rebase trunk

@paulopmt1

Copy link
Copy Markdown
Contributor Author

Running an AI adversarial review resulted in the following findings:

  • ReactDOM.render is still being used here:
  • apps/odyssey-stats/tsconfig.json references ../../client/tsconfig-reference.json, but this PR makes that referenced config inherit composite: false from client/tsconfig.json. It's worth confirming if this change breaks the Odyssey Stats typechecking. Referenced projects are meant to have setting "composite": true. (https://www.typescriptlang.org/docs/handbook/project-references.html#composite)
  • As to the suppression @testing-library/react no longer bundles its @testing-library/dom peer, which viewport-react and i18n-calypso don't provide. Is the right fix to add @testing-library/dom as devDependencies to i18n-calypso and viewport-react? In that case, might make sense to have a follow-up PR.
  1. ReactDOM.render addressed here
  2. apps/odyssey-stats/tsconfig.json addressed here
  3. Yes, I'll address that in a follow-up

Thanks for this additional check @taipeicoder, it was super helpful!

Copilot AI commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request and rebase trunk

Rebased this locally onto current trunk at 8f5fab8 and resolved the ResponsiveMenu conflict, but I couldn’t update the PR branch because the available push path rejects the required non-fast-forward update. If you want, I can instead update the branch with a merge-from-trunk commit that will push cleanly.

# Conflicts:
#	client/dashboard/components/responsive-menu/index.tsx
@paulopmt1

Copy link
Copy Markdown
Contributor Author

This is ready and will be deployed Monday Morning. Context: p1783039897732629/1782841690.373669-slack-C02DQP0FP

@paulopmt1 paulopmt1 added DO NOT MERGE and removed [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. labels Jul 3, 2026
@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jul 6, 2026
@paulopmt1
paulopmt1 added this pull request to the merge queue Jul 6, 2026
Merged via the queue into trunk with commit cd149b8 Jul 6, 2026
15 checks passed
@paulopmt1
paulopmt1 deleted the update/react-19 branch July 6, 2026 17:17
@github-actions github-actions Bot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Jul 6, 2026
pull Bot pushed a commit to MaxMood96/wp-calypso that referenced this pull request Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants