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