Skip to content

Commit

Permalink
Classes consume ref prop during SSR, too
Browse files Browse the repository at this point in the history
Same as facebook#28719 but for SSR.
  • Loading branch information
acdlite committed Apr 3, 2024
1 parent 7a2609e commit 153d2d6
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -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 <Text text={'render: ' + getPropKeys(this.props)} />;
}
}

Component.defaultProps = {
default: 'yo',
};

// `ref` should never appear as a prop. `default` always should.
const ref = React.createRef();
const stream = await ReactDOMServer.renderToReadableStream(
<Component text="Yay" ref={ref} />,
);
await readIntoContainer(stream);
assertLog([
'constructor: text, default',
'componentWillMount: text, default',
'render: text, default',
]);
});
});
37 changes: 34 additions & 3 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1390,21 +1390,52 @@ 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,
keyPath: KeyNode,
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;
}

Expand Down

0 comments on commit 153d2d6

Please sign in to comment.