@samitouri / QOS-React / commits / 3761acb42b

Classes consume ref prop during SSR, too (#28731)

Same as #28719 but for SSR.

Andrew Clark committed Apr 3, 2024 at 12:55 UTC 3761acb42bf9979314fff130d4d9505408bcb651
2 files changed +128 -3
packages/react-dom/src/__tests__/ReactClassComponentPropResolutionFizz-test.js new
+94
@@ -0,0 +1,94 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +import {insertNodesAndExecuteScripts} from '../test-utils/FizzTestUtils';
13 +
14 +// Polyfills for test environment
15 +global.ReadableStream =
16 + require('web-streams-polyfill/ponyfill/es6').ReadableStream;
17 +global.TextEncoder = require('util').TextEncoder;
18 +
19 +let React;
20 +let ReactDOMServer;
21 +let Scheduler;
22 +let assertLog;
23 +let container;
24 +
25 +describe('ReactClassComponentPropResolutionFizz', () => {
26 + beforeEach(() => {
27 + jest.resetModules();
28 + React = require('react');
29 + Scheduler = require('scheduler');
30 + ReactDOMServer = require('react-dom/server.browser');
31 + assertLog = require('internal-test-utils').assertLog;
32 + container = document.createElement('div');
33 + document.body.appendChild(container);
34 + });
35 +
36 + afterEach(() => {
37 + document.body.removeChild(container);
38 + });
39 +
40 + async function readIntoContainer(stream) {
41 + const reader = stream.getReader();
42 + let result = '';
43 + while (true) {
44 + const {done, value} = await reader.read();
45 + if (done) {
46 + break;
47 + }
48 + result += Buffer.from(value).toString('utf8');
49 + }
50 + const temp = document.createElement('div');
51 + temp.innerHTML = result;
52 + insertNodesAndExecuteScripts(temp, container, null);
53 + }
54 +
55 + function Text({text}) {
56 + Scheduler.log(text);
57 + return text;
58 + }
59 +
60 + test('resolves ref and default props before calling lifecycle methods', async () => {
61 + function getPropKeys(props) {
62 + return Object.keys(props).join(', ');
63 + }
64 +
65 + class Component extends React.Component {
66 + constructor(props) {
67 + super(props);
68 + Scheduler.log('constructor: ' + getPropKeys(props));
69 + }
70 + UNSAFE_componentWillMount() {
71 + Scheduler.log('componentWillMount: ' + getPropKeys(this.props));
72 + }
73 + render() {
74 + return <Text text={'render: ' + getPropKeys(this.props)} />;
75 + }
76 + }
77 +
78 + Component.defaultProps = {
79 + default: 'yo',
80 + };
81 +
82 + // `ref` should never appear as a prop. `default` always should.
83 + const ref = React.createRef();
84 + const stream = await ReactDOMServer.renderToReadableStream(
85 + <Component text="Yay" ref={ref} />,
86 + );
87 + await readIntoContainer(stream);
88 + assertLog([
89 + 'constructor: text, default',
90 + 'componentWillMount: text, default',
91 + 'render: text, default',
92 + ]);
93 + });
94 +});
packages/react-server/src/ReactFizzServer.js
+34 -3
@@ -1390,6 +1390,25 @@ function finishClassComponent(
1390 task.keyPath = prevKeyPath;
1391 }
1392
1393 +export function resolveClassComponentProps(
1394 + Component: any,
1395 + baseProps: Object,
1396 +): Object {
1397 + let newProps = baseProps;
1398 +
1399 + // TODO: This is where defaultProps should be resolved, too.
1400 +
1401 + if (enableRefAsProp) {
1402 + // Remove ref from the props object, if it exists.
1403 + if ('ref' in newProps) {
1404 + newProps = assign({}, newProps);
1405 + delete newProps.ref;
1406 + }
1407 + }
1408 +
1409 + return newProps;
1410 +}
1411 +
1412 function renderClassComponent(
1413 request: Request,
1414 task: Task,
@@ -1397,14 +1416,26 @@ function renderClassComponent(
1416 Component: any,
1417 props: any,
1418 ): void {
1419 + const resolvedProps = resolveClassComponentProps(Component, props);
1420 const previousComponentStack = task.componentStack;
1421 task.componentStack = createClassComponentStack(task, Component);
1422 const maskedContext = !disableLegacyContext
1423 ? getMaskedContext(Component, task.legacyContext)
1424 : undefined;
1405 - const instance = constructClassInstance(Component, props, maskedContext);
1406 - mountClassInstance(instance, Component, props, maskedContext);
1407 - finishClassComponent(request, task, keyPath, instance, Component, props);
1425 + const instance = constructClassInstance(
1426 + Component,
1427 + resolvedProps,
1428 + maskedContext,
1429 + );
1430 + mountClassInstance(instance, Component, resolvedProps, maskedContext);
1431 + finishClassComponent(
1432 + request,
1433 + task,
1434 + keyPath,
1435 + instance,
1436 + Component,
1437 + resolvedProps,
1438 + );
1439 task.componentStack = previousComponentStack;
1440 }
1441