diff --git a/packages/react-dom/src/__tests__/ReactClassComponentPropResolutionFizz-test.js b/packages/react-dom/src/__tests__/ReactClassComponentPropResolutionFizz-test.js
new file mode 100644
index 0000000000000..653797ec44b00
--- /dev/null
+++ b/packages/react-dom/src/__tests__/ReactClassComponentPropResolutionFizz-test.js
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @emails react-core
+ */
+
+'use strict';
+
+import {insertNodesAndExecuteScripts} from '../test-utils/FizzTestUtils';
+
+// Polyfills for test environment
+global.ReadableStream =
+ require('web-streams-polyfill/ponyfill/es6').ReadableStream;
+global.TextEncoder = require('util').TextEncoder;
+
+let React;
+let ReactDOMServer;
+let Scheduler;
+let assertLog;
+let container;
+
+describe('ReactClassComponentPropResolutionFizz', () => {
+ beforeEach(() => {
+ jest.resetModules();
+ React = require('react');
+ Scheduler = require('scheduler');
+ ReactDOMServer = require('react-dom/server.browser');
+ assertLog = require('internal-test-utils').assertLog;
+ container = document.createElement('div');
+ document.body.appendChild(container);
+ });
+
+ afterEach(() => {
+ document.body.removeChild(container);
+ });
+
+ async function readIntoContainer(stream) {
+ const reader = stream.getReader();
+ let result = '';
+ while (true) {
+ const {done, value} = await reader.read();
+ if (done) {
+ break;
+ }
+ result += Buffer.from(value).toString('utf8');
+ }
+ const temp = document.createElement('div');
+ temp.innerHTML = result;
+ insertNodesAndExecuteScripts(temp, container, null);
+ }
+
+ function Text({text}) {
+ Scheduler.log(text);
+ return text;
+ }
+
+ test('resolves ref and default props before calling lifecycle methods', async () => {
+ function getPropKeys(props) {
+ return Object.keys(props).join(', ');
+ }
+
+ class Component extends React.Component {
+ constructor(props) {
+ super(props);
+ Scheduler.log('constructor: ' + getPropKeys(props));
+ }
+ UNSAFE_componentWillMount() {
+ Scheduler.log('componentWillMount: ' + getPropKeys(this.props));
+ }
+ render() {
+ return ;
+ }
+ }
+
+ Component.defaultProps = {
+ default: 'yo',
+ };
+
+ // `ref` should never appear as a prop. `default` always should.
+ const ref = React.createRef();
+ const stream = await ReactDOMServer.renderToReadableStream(
+ ,
+ );
+ await readIntoContainer(stream);
+ assertLog([
+ 'constructor: text, default',
+ 'componentWillMount: text, default',
+ 'render: text, default',
+ ]);
+ });
+});
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index 7d5659981e400..529eaef8a692b 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -1390,6 +1390,25 @@ function finishClassComponent(
task.keyPath = prevKeyPath;
}
+export function resolveClassComponentProps(
+ Component: any,
+ baseProps: Object,
+): Object {
+ let newProps = baseProps;
+
+ // TODO: This is where defaultProps should be resolved, too.
+
+ if (enableRefAsProp) {
+ // Remove ref from the props object, if it exists.
+ if ('ref' in newProps) {
+ newProps = assign({}, newProps);
+ delete newProps.ref;
+ }
+ }
+
+ return newProps;
+}
+
function renderClassComponent(
request: Request,
task: Task,
@@ -1397,14 +1416,26 @@ function renderClassComponent(
Component: any,
props: any,
): void {
+ const resolvedProps = resolveClassComponentProps(Component, props);
const previousComponentStack = task.componentStack;
task.componentStack = createClassComponentStack(task, Component);
const maskedContext = !disableLegacyContext
? getMaskedContext(Component, task.legacyContext)
: undefined;
- const instance = constructClassInstance(Component, props, maskedContext);
- mountClassInstance(instance, Component, props, maskedContext);
- finishClassComponent(request, task, keyPath, instance, Component, props);
+ const instance = constructClassInstance(
+ Component,
+ resolvedProps,
+ maskedContext,
+ );
+ mountClassInstance(instance, Component, resolvedProps, maskedContext);
+ finishClassComponent(
+ request,
+ task,
+ keyPath,
+ instance,
+ Component,
+ resolvedProps,
+ );
task.componentStack = previousComponentStack;
}