diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js
index 4f717c7a48d29..71d062a458990 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js
@@ -14,6 +14,11 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio
let React;
let ReactDOM;
let ReactDOMServer;
+let forwardRef;
+let pure;
+let yieldedValues;
+let yieldValue;
+let clearYields;
function initModules() {
// Reset warning cache.
@@ -21,6 +26,18 @@ function initModules() {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
+ forwardRef = React.forwardRef;
+ pure = React.pure;
+
+ yieldedValues = [];
+ yieldValue = value => {
+ yieldedValues.push(value);
+ };
+ clearYields = () => {
+ const ret = yieldedValues;
+ yieldedValues = [];
+ return ret;
+ };
// Make them available to the helpers.
return {
@@ -40,7 +57,7 @@ describe('ReactDOMServerIntegration', () => {
const FunctionComponent = ({label, forwardedRef}) => (
{label}
);
- const WrappedFunctionComponent = React.forwardRef((props, ref) => (
+ const WrappedFunctionComponent = forwardRef((props, ref) => (
));
@@ -65,4 +82,57 @@ describe('ReactDOMServerIntegration', () => {
expect(div.tagName).toBe('DIV');
expect(div.textContent).toBe('Test');
});
+
+ describe('pure functional components', () => {
+ beforeEach(() => {
+ resetModules();
+ });
+
+ function Text({text}) {
+ yieldValue(text);
+ return {text};
+ }
+
+ function Counter({count}) {
+ return ;
+ }
+
+ itRenders('basic render', async render => {
+ const PureCounter = pure(Counter);
+ const domNode = await render();
+ expect(domNode.textContent).toEqual('Count: 0');
+ });
+
+ itRenders('composition with forwardRef', async render => {
+ const RefCounter = (props, ref) => ;
+ const PureRefCounter = pure(forwardRef(RefCounter));
+
+ const ref = React.createRef();
+ ref.current = 0;
+ await render();
+
+ expect(clearYields()).toEqual(['Count: 0']);
+ });
+
+ itRenders('with comparator', async render => {
+ const PureCounter = pure(Counter, (oldProps, newProps) => false);
+ await render();
+ expect(clearYields()).toEqual(['Count: 0']);
+ });
+
+ itRenders(
+ 'comparator functions are not invoked on the server',
+ async render => {
+ const PureCounter = React.pure(Counter, (oldProps, newProps) => {
+ yieldValue(
+ `Old count: ${oldProps.count}, New count: ${newProps.count}`,
+ );
+ return oldProps.count === newProps.count;
+ });
+
+ await render();
+ expect(clearYields()).toEqual(['Count: 0']);
+ },
+ );
+ });
});
diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js
index c8fc0ea147e28..c45e569452830 100644
--- a/packages/react-dom/src/server/ReactPartialRenderer.js
+++ b/packages/react-dom/src/server/ReactPartialRenderer.js
@@ -39,6 +39,7 @@ import {
REACT_PROVIDER_TYPE,
REACT_CONTEXT_TYPE,
REACT_LAZY_TYPE,
+ REACT_PURE_TYPE,
} from 'shared/ReactSymbols';
import {
@@ -1001,6 +1002,28 @@ class ReactDOMServerRenderer {
this.stack.push(frame);
return '';
}
+ case REACT_PURE_TYPE: {
+ const element: ReactElement = ((nextChild: any): ReactElement);
+ let nextChildren = [
+ React.createElement(
+ elementType.type,
+ Object.assign({ref: element.ref}, element.props),
+ ),
+ ];
+ const frame: Frame = {
+ type: null,
+ domNamespace: parentNamespace,
+ children: nextChildren,
+ childIndex: 0,
+ context: context,
+ footer: '',
+ };
+ if (__DEV__) {
+ ((frame: any): FrameDev).debugElementStack = [];
+ }
+ this.stack.push(frame);
+ return '';
+ }
case REACT_PROVIDER_TYPE: {
const provider: ReactProvider = (nextChild: any);
const nextProps = provider.props;