Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/e2e-tests/plugins/react-18-compat-block/editor.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
( function () {
const { registerBlockType } = wp.blocks;
const { useBlockProps } = wp.blockEditor;
const { createElement, memo } = wp.element;

// The externalized React JSX runtime (the `react-jsx-runtime` script
// handle). When the React 19 experiment is enabled this is React 19's
Expand Down Expand Up @@ -89,6 +90,23 @@
label: 'Hello from defaultProps',
};

// The same, wrapped in `memo()`. React 18 resolved the inner component's
// `defaultProps` in `updateMemoComponent`, not at element creation, so no
// runtime ever wrote them into the element. All three ways of creating the
// element are exercised — the classic `createElement`, the externalized JSX
// runtime, and the bundled React 18 one — because the fix has to live in
// the reconciler to cover the last of them.
function MemoGreetingBase( props ) {
return jsx( 'span', {
className: props.className,
children: props.label,
} );
}
MemoGreetingBase.defaultProps = {
label: 'Hello from memo defaultProps',
};
const MemoGreeting = memo( MemoGreetingBase );

registerBlockType( 'test/react-18-compat-block', {
apiVersion: 3,
edit: function Edit() {
Expand Down Expand Up @@ -122,6 +140,26 @@
// The `defaultProps` component, created with the
// externalized React runtime.
jsx( Greeting, {}, 'greeting' ),
createElement( MemoGreeting, {
key: 'memo-greeting',
className: 'react-18-compat-block__memo-greeting',
} ),
jsx(
MemoGreeting,
{
className:
'react-18-compat-block__memo-greeting-jsx',
},
'memo-greeting-jsx'
),
jsxDEV(
MemoGreeting,
{
className:
'react-18-compat-block__memo-greeting-legacy',
},
'memo-greeting-legacy'
),
],
} );
},
Expand Down
18 changes: 16 additions & 2 deletions test/e2e/specs/editor/plugins/react-18-compat-block.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );

// This block deliberately exercises legacy React 18 patterns (a bundled React
// 18 development JSX runtime that produces `Symbol.for( 'react.element' )`
// elements, a function component relying on `defaultProps`, and the `inert`
// attribute) while the editor runs on the externalized React 19 runtime.
// elements, function components relying on `defaultProps` both plain and
// wrapped in `memo()`, and the `inert` attribute) while the editor runs on the
// externalized React 19 runtime.
//
// It is expected to FAIL until the React 19 compatibility layer can render
// elements created by an older React runtime and restore `defaultProps`
Expand Down Expand Up @@ -46,6 +47,19 @@ test.describe( 'React 18 compatibility block (React 19 runtime)', () => {
block.locator( '.react-18-compat-block__greeting' )
).toHaveText( 'Hello from defaultProps' );

// `defaultProps` declared on the component inside a `memo()` wrapper
// are resolved too, whichever runtime created the element — including
// the bundled React 18 one, which never wrote them into the element.
await expect(
block.locator( '.react-18-compat-block__memo-greeting' )
).toHaveText( 'Hello from memo defaultProps' );
await expect(
block.locator( '.react-18-compat-block__memo-greeting-jsx' )
).toHaveText( 'Hello from memo defaultProps' );
await expect(
block.locator( '.react-18-compat-block__memo-greeting-legacy' )
).toHaveText( 'Hello from memo defaultProps' );

// `inert=''` attribute specified as string is rendered as `true` by a patched React 19 runtime.
const inertString = block.locator(
'.react-18-compat-block__inert-string'
Expand Down
7 changes: 5 additions & 2 deletions tools/build-scripts/packages/build-vendors.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
patchTransitionalSymbols,
patchCoerceRef,
patchInertAttribute,
patchMemoDefaultProps,
} from './codemod-react-legacy-element.mjs';

const __dirname = path.dirname( fileURLToPath( import.meta.url ) );
Expand All @@ -22,12 +23,14 @@ const WORKSPACE_DIR = path.resolve( __dirname, '..' );

// Patchers that adapt React 19 to also accept legacy (React 17/18) elements.
// The `react` core bundle only needs the element-symbol patch, while the
// `react-dom` bundle additionally needs the DOM-only ref and `inert` patches.
// `react-dom` bundle additionally needs the DOM-only ref and `inert` patches,
// and the reconciler patch that restores `memo()` `defaultProps`.
const patchReact = createPatcher( patchTransitionalSymbols );
const patchReactDOM = createPatcher(
patchTransitionalSymbols,
patchCoerceRef,
patchInertAttribute
patchInertAttribute,
patchMemoDefaultProps
);

const requireFromWorkspace = createRequire(
Expand Down
Loading
Loading