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;