@samitouri / QOS-React-2 / commits / d4d7864934

Fix `ReactFabricHostComponent` methods if detached (#21967)

Timothy Yung committed Jul 26, 2021 at 21:02 UTC d4d7864934025232e8bca5d2febec846ec1bd6e3
2 files changed +201 -14
packages/react-native-renderer/src/ReactFabricHostConfig.js
+26 -14
@@ -137,17 +137,23 @@ class ReactFabricHostComponent {
137 }
138
139 measure(callback: MeasureOnSuccessCallback) {
140 - fabricMeasure(
141 - this._internalInstanceHandle.stateNode.node,
142 - mountSafeCallback_NOT_REALLY_SAFE(this, callback),
143 - );
140 + const {stateNode} = this._internalInstanceHandle;
141 + if (stateNode != null) {
142 + fabricMeasure(
143 + stateNode.node,
144 + mountSafeCallback_NOT_REALLY_SAFE(this, callback),
145 + );
146 + }
147 }
148
149 measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
147 - fabricMeasureInWindow(
148 - this._internalInstanceHandle.stateNode.node,
149 - mountSafeCallback_NOT_REALLY_SAFE(this, callback),
150 - );
150 + const {stateNode} = this._internalInstanceHandle;
151 + if (stateNode != null) {
152 + fabricMeasureInWindow(
153 + stateNode.node,
154 + mountSafeCallback_NOT_REALLY_SAFE(this, callback),
155 + );
156 + }
157 }
158
159 measureLayout(
@@ -168,12 +174,18 @@ class ReactFabricHostComponent {
174 return;
175 }
176
171 - fabricMeasureLayout(
172 - this._internalInstanceHandle.stateNode.node,
173 - relativeToNativeNode._internalInstanceHandle.stateNode.node,
174 - mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
175 - mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
176 - );
177 + const toStateNode = this._internalInstanceHandle.stateNode;
178 + const fromStateNode =
179 + relativeToNativeNode._internalInstanceHandle.stateNode;
180 +
181 + if (toStateNode != null && fromStateNode != null) {
182 + fabricMeasureLayout(
183 + toStateNode.node,
184 + fromStateNode.node,
185 + mountSafeCallback_NOT_REALLY_SAFE(this, onFail),
186 + mountSafeCallback_NOT_REALLY_SAFE(this, onSuccess),
187 + );
188 + }
189 }
190
191 setNativeProps(nativeProps: Object) {
packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
+175
@@ -414,6 +414,37 @@ describe('ReactFabric', () => {
414 expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100, 0, 0);
415 });
416
417 + it('should no-op if calling measure on unmounted refs', () => {
418 + const View = createReactNativeComponentClass('RCTView', () => ({
419 + validAttributes: {foo: true},
420 + uiViewClassName: 'RCTView',
421 + }));
422 +
423 + nativeFabricUIManager.measure.mockClear();
424 +
425 + let viewRef;
426 + act(() => {
427 + ReactFabric.render(
428 + <View
429 + ref={ref => {
430 + viewRef = ref;
431 + }}
432 + />,
433 + 11,
434 + );
435 + });
436 + const dangerouslyRetainedViewRef = viewRef;
437 + act(() => {
438 + ReactFabric.stopSurface(11);
439 + });
440 +
441 + expect(nativeFabricUIManager.measure).not.toBeCalled();
442 + const successCallback = jest.fn();
443 + dangerouslyRetainedViewRef.measure(successCallback);
444 + expect(nativeFabricUIManager.measure).not.toBeCalled();
445 + expect(successCallback).not.toBeCalled();
446 + });
447 +
448 it('should call FabricUIManager.measureInWindow on ref.measureInWindow', () => {
449 const View = createReactNativeComponentClass('RCTView', () => ({
450 validAttributes: {foo: true},
@@ -442,6 +473,37 @@ describe('ReactFabric', () => {
473 expect(successCallback).toHaveBeenCalledWith(10, 10, 100, 100);
474 });
475
476 + it('should no-op if calling measureInWindow on unmounted refs', () => {
477 + const View = createReactNativeComponentClass('RCTView', () => ({
478 + validAttributes: {foo: true},
479 + uiViewClassName: 'RCTView',
480 + }));
481 +
482 + nativeFabricUIManager.measureInWindow.mockClear();
483 +
484 + let viewRef;
485 + act(() => {
486 + ReactFabric.render(
487 + <View
488 + ref={ref => {
489 + viewRef = ref;
490 + }}
491 + />,
492 + 11,
493 + );
494 + });
495 + const dangerouslyRetainedViewRef = viewRef;
496 + act(() => {
497 + ReactFabric.stopSurface(11);
498 + });
499 +
500 + expect(nativeFabricUIManager.measureInWindow).not.toBeCalled();
501 + const successCallback = jest.fn();
502 + dangerouslyRetainedViewRef.measureInWindow(successCallback);
503 + expect(nativeFabricUIManager.measureInWindow).not.toBeCalled();
504 + expect(successCallback).not.toBeCalled();
505 + });
506 +
507 it('should support ref in ref.measureLayout', () => {
508 const View = createReactNativeComponentClass('RCTView', () => ({
509 validAttributes: {foo: true},
@@ -480,6 +542,119 @@ describe('ReactFabric', () => {
542 expect(successCallback).toHaveBeenCalledWith(1, 1, 100, 100);
543 });
544
545 + it('should no-op if calling measureLayout on unmounted "from" ref', () => {
546 + const View = createReactNativeComponentClass('RCTView', () => ({
547 + validAttributes: {foo: true},
548 + uiViewClassName: 'RCTView',
549 + }));
550 +
551 + nativeFabricUIManager.measureLayout.mockClear();
552 +
553 + let viewRef;
554 + let otherRef;
555 + act(() => {
556 + ReactFabric.render(
557 + <View>
558 + <View
559 + foo="bar"
560 + ref={ref => {
561 + viewRef = ref;
562 + }}
563 + />
564 + <View
565 + ref={ref => {
566 + otherRef = ref;
567 + }}
568 + />
569 + </View>,
570 + 11,
571 + );
572 + });
573 + const dangerouslyRetainedOtherRef = otherRef;
574 + act(() => {
575 + ReactFabric.render(
576 + <View>
577 + <View
578 + foo="bar"
579 + ref={ref => {
580 + viewRef = ref;
581 + }}
582 + />
583 + {null}
584 + </View>,
585 + 11,
586 + );
587 + });
588 +
589 + expect(nativeFabricUIManager.measureLayout).not.toBeCalled();
590 + const successCallback = jest.fn();
591 + const failureCallback = jest.fn();
592 + viewRef.measureLayout(
593 + dangerouslyRetainedOtherRef,
594 + successCallback,
595 + failureCallback,
596 + );
597 + expect(nativeFabricUIManager.measureLayout).not.toBeCalled();
598 + expect(successCallback).not.toBeCalled();
599 + expect(failureCallback).not.toBeCalled();
600 + });
601 +
602 + it('should no-op if calling measureLayout on unmounted "to" ref', () => {
603 + const View = createReactNativeComponentClass('RCTView', () => ({
604 + validAttributes: {foo: true},
605 + uiViewClassName: 'RCTView',
606 + }));
607 +
608 + nativeFabricUIManager.measureLayout.mockClear();
609 +
610 + let viewRef;
611 + let otherRef;
612 + act(() => {
613 + ReactFabric.render(
614 + <View>
615 + <View
616 + foo="bar"
617 + ref={ref => {
618 + viewRef = ref;
619 + }}
620 + />
621 + <View
622 + ref={ref => {
623 + otherRef = ref;
624 + }}
625 + />
626 + </View>,
627 + 11,
628 + );
629 + });
630 + const dangerouslyRetainedViewRef = viewRef;
631 + act(() => {
632 + ReactFabric.render(
633 + <View>
634 + {null}
635 + <View
636 + ref={ref => {
637 + otherRef = ref;
638 + }}
639 + />
640 + </View>,
641 + 11,
642 + );
643 + });
644 +
645 + expect(nativeFabricUIManager.measureLayout).not.toBeCalled();
646 + const successCallback = jest.fn();
647 + const failureCallback = jest.fn();
648 + dangerouslyRetainedViewRef.measureLayout(
649 + otherRef,
650 + successCallback,
651 + failureCallback,
652 + );
653 + expect(nativeFabricUIManager.measureLayout).not.toBeCalled();
654 + expect(successCallback).not.toBeCalled();
655 + expect(failureCallback).not.toBeCalled();
656 + });
657 +
658 it('returns the correct instance and calls it in the callback', () => {
659 const View = createReactNativeComponentClass('RCTView', () => ({
660 validAttributes: {foo: true},