Skip to content

Commit 3cf899e

Browse files
committed
Add support for React.pure in ReactDOMServer
1 parent 21a79a1 commit 3cf899e

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

packages/react-dom/src/__tests__/ReactDOMServerIntegrationSpecialTypes-test.js

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,30 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio
1414
let React;
1515
let ReactDOM;
1616
let ReactDOMServer;
17+
let forwardRef;
18+
let pure;
19+
let yieldedValues;
20+
let yieldValue;
21+
let clearYields;
1722

1823
function initModules() {
1924
// Reset warning cache.
2025
jest.resetModuleRegistry();
2126
React = require('react');
2227
ReactDOM = require('react-dom');
2328
ReactDOMServer = require('react-dom/server');
29+
forwardRef = React.forwardRef;
30+
pure = React.pure;
31+
32+
yieldedValues = [];
33+
yieldValue = value => {
34+
yieldedValues.push(value);
35+
};
36+
clearYields = () => {
37+
const ret = yieldedValues;
38+
yieldedValues = [];
39+
return ret;
40+
};
2441

2542
// Make them available to the helpers.
2643
return {
@@ -40,7 +57,7 @@ describe('ReactDOMServerIntegration', () => {
4057
const FunctionComponent = ({label, forwardedRef}) => (
4158
<div ref={forwardedRef}>{label}</div>
4259
);
43-
const WrappedFunctionComponent = React.forwardRef((props, ref) => (
60+
const WrappedFunctionComponent = forwardRef((props, ref) => (
4461
<FunctionComponent {...props} forwardedRef={ref} />
4562
));
4663

@@ -65,4 +82,57 @@ describe('ReactDOMServerIntegration', () => {
6582
expect(div.tagName).toBe('DIV');
6683
expect(div.textContent).toBe('Test');
6784
});
85+
86+
describe('pure functional components', () => {
87+
beforeEach(() => {
88+
resetModules();
89+
});
90+
91+
function Text({text}) {
92+
yieldValue(text);
93+
return <span>{text}</span>;
94+
}
95+
96+
function Counter({count}) {
97+
return <Text text={'Count: ' + count} />;
98+
}
99+
100+
itRenders('basic render', async render => {
101+
const PureCounter = pure(Counter);
102+
const domNode = await render(<PureCounter count={0} />);
103+
expect(domNode.textContent).toEqual('Count: 0');
104+
});
105+
106+
itRenders('composition with forwardRef', async render => {
107+
const RefCounter = (props, ref) => <Counter count={ref.current} />;
108+
const PureRefCounter = pure(forwardRef(RefCounter));
109+
110+
const ref = React.createRef();
111+
ref.current = 0;
112+
await render(<PureRefCounter ref={ref} />);
113+
114+
expect(clearYields()).toEqual(['Count: 0']);
115+
});
116+
117+
itRenders('with comparator', async render => {
118+
const PureCounter = pure(Counter, (oldProps, newProps) => false);
119+
await render(<PureCounter count={0} />);
120+
expect(clearYields()).toEqual(['Count: 0']);
121+
});
122+
123+
itRenders(
124+
'comparator functions are not invoked on the server',
125+
async render => {
126+
const PureCounter = React.pure(Counter, (oldProps, newProps) => {
127+
yieldValue(
128+
`Old count: ${oldProps.count}, New count: ${newProps.count}`,
129+
);
130+
return oldProps.count === newProps.count;
131+
});
132+
133+
await render(<PureCounter count={0} />);
134+
expect(clearYields()).toEqual(['Count: 0']);
135+
},
136+
);
137+
});
68138
});

packages/react-dom/src/server/ReactPartialRenderer.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
REACT_PROFILER_TYPE,
3939
REACT_PROVIDER_TYPE,
4040
REACT_CONTEXT_TYPE,
41+
REACT_PURE_TYPE,
4142
} from 'shared/ReactSymbols';
4243

4344
import {
@@ -964,6 +965,34 @@ class ReactDOMServerRenderer {
964965
this.stack.push(frame);
965966
return '';
966967
}
968+
case REACT_PURE_TYPE: {
969+
const element: ReactElement = ((nextChild: any): ReactElement);
970+
let nextChildren;
971+
if (
972+
typeof elementType.render === 'object' && elementType.render !== null && elementType.render.$$typeof === REACT_FORWARD_REF_TYPE
973+
) {
974+
nextChildren = toArray(
975+
elementType.render.render(element.props, element.ref),
976+
);
977+
} else {
978+
nextChildren = toArray(
979+
elementType.render(element.props),
980+
);
981+
}
982+
const frame: Frame = {
983+
type: null,
984+
domNamespace: parentNamespace,
985+
children: nextChildren,
986+
childIndex: 0,
987+
context: context,
988+
footer: '',
989+
};
990+
if (__DEV__) {
991+
((frame: any): FrameDev).debugElementStack = [];
992+
}
993+
this.stack.push(frame);
994+
return '';
995+
}
967996
case REACT_PROVIDER_TYPE: {
968997
const provider: ReactProvider<any> = (nextChild: any);
969998
const nextProps = provider.props;

0 commit comments

Comments
 (0)