@samitouri / QOS-React / commits / d883d59863

forwardRef() components should not re-render on deep setState() (#12690)

* Add a failing test for forwardRef memoization * Memoize forwardRef props and bail out on strict equality * Bail out only when ref matches the current ref

Dan Abramov committed Apr 26, 2018 at 19:47 UTC d883d59863483a1fb9f3dd0455022e4c6e1a41d5
2 files changed +46 -5
packages/react-reconciler/src/ReactFiberBeginWork.js
+13 -5
@@ -169,12 +169,20 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
169
170 function updateForwardRef(current, workInProgress) {
171 const render = workInProgress.type.render;
172 - const nextChildren = render(
173 - workInProgress.pendingProps,
174 - workInProgress.ref,
175 - );
172 + const nextProps = workInProgress.pendingProps;
173 + const ref = workInProgress.ref;
174 + if (hasLegacyContextChanged()) {
175 + // Normally we can bail out on props equality but if context has changed
176 + // we don't do the bailout and we have to reuse existing props instead.
177 + } else if (workInProgress.memoizedProps === nextProps) {
178 + const currentRef = current !== null ? current.ref : null;
179 + if (ref === currentRef) {
180 + return bailoutOnAlreadyFinishedWork(current, workInProgress);
181 + }
182 + }
183 + const nextChildren = render(nextProps, ref);
184 reconcileChildren(current, workInProgress, nextChildren);
177 - memoizeProps(workInProgress, nextChildren);
185 + memoizeProps(workInProgress, nextProps);
186 return workInProgress.child;
187 }
188
packages/react/src/__tests__/forwardRef-test.internal.js
+33
@@ -232,6 +232,39 @@ describe('forwardRef', () => {
232 expect(ref.current).toBe(null);
233 });
234
235 + it('should not re-run the render callback on a deep setState', () => {
236 + let inst;
237 +
238 + class Inner extends React.Component {
239 + render() {
240 + ReactNoop.yield('Inner');
241 + inst = this;
242 + return <div ref={this.props.forwardedRef} />;
243 + }
244 + }
245 +
246 + function Middle(props) {
247 + ReactNoop.yield('Middle');
248 + return <Inner {...props} />;
249 + }
250 +
251 + const Forward = React.forwardRef((props, ref) => {
252 + ReactNoop.yield('Forward');
253 + return <Middle {...props} forwardedRef={ref} />;
254 + });
255 +
256 + function App() {
257 + ReactNoop.yield('App');
258 + return <Forward />;
259 + }
260 +
261 + ReactNoop.render(<App />);
262 + expect(ReactNoop.flush()).toEqual(['App', 'Forward', 'Middle', 'Inner']);
263 +
264 + inst.setState({});
265 + expect(ReactNoop.flush()).toEqual(['Inner']);
266 + });
267 +
268 it('should warn if not provided a callback during creation', () => {
269 expect(() => React.forwardRef(undefined)).toWarnDev(
270 'forwardRef requires a render function but was given undefined.',