[fix] ScrollView ref and methods

Make sure the 'ref' for a ScrollView is a host node with React Native's
proprietary and legacy instance methods attached.

Fix #1957
This commit is contained in:
Nicolas Gallagher
2021-03-30 18:58:24 -07:00
parent fc4cc216a1
commit ae87e1e459
2 changed files with 86 additions and 11 deletions
@@ -38,4 +38,52 @@ describe('components/ScrollView', () => {
expect(onScroll).not.toBeCalled();
});
});
describe('prop "ref"', () => {
test('value is set', () => {
const ref = jest.fn();
render(<ScrollView ref={ref} />);
expect(ref).toBeCalled();
});
test('is not called for prop changes', () => {
const ref = jest.fn();
let rerender;
act(() => {
({ rerender } = render(<ScrollView nativeID="123" ref={ref} style={{ borderWidth: 5 }} />));
});
expect(ref).toHaveBeenCalledTimes(1);
act(() => {
rerender(<ScrollView nativeID="1234" ref={ref} style={{ borderWidth: 6 }} />);
});
expect(ref).toHaveBeenCalledTimes(1);
});
test('node has imperative methods', () => {
const ref = React.createRef();
act(() => {
render(<ScrollView ref={ref} />);
});
const node = ref.current;
// Did we get an HTMLElement?
expect(node.tagName === 'DIV').toBe(true);
// Does it have the "platform" methods?
expect(typeof node.measure === 'function').toBe(true);
expect(typeof node.measureLayout === 'function').toBe(true);
expect(typeof node.measureInWindow === 'function').toBe(true);
expect(typeof node.setNativeProps === 'function').toBe(true);
// Does it have the scrollview methods?
expect(typeof node.getScrollResponder === 'function').toBe(true);
expect(typeof node.getScrollableNode === 'function').toBe(true);
expect(typeof node.getInnerViewNode === 'function').toBe(true);
expect(typeof node.getInnerViewRef === 'function').toBe(true);
expect(typeof node.getNativeScrollRef === 'function').toBe(true);
expect(typeof node.scrollTo === 'function').toBe(true);
expect(typeof node.scrollToEnd === 'function').toBe(true);
expect(typeof node.flashScrollIndicators === 'function').toBe(true);
expect(typeof node.scrollResponderZoomTo === 'function').toBe(true);
expect(typeof node.scrollResponderScrollNativeHandleToKeyboard === 'function').toBe(true);
});
});
});
+38 -11
View File
@@ -13,6 +13,7 @@ import type { ViewProps, ViewStyle } from '../View/types';
import createReactClass from 'create-react-class';
import dismissKeyboard from '../../modules/dismissKeyboard';
import invariant from 'fbjs/lib/invariant';
import mergeRefs from '../../modules/mergeRefs';
import ScrollResponder from '../../modules/ScrollResponder';
import ScrollViewBase from './ScrollViewBase';
import StyleSheet from '../StyleSheet';
@@ -47,12 +48,6 @@ const ScrollView = ((createReactClass({
this.scrollResponderFlashScrollIndicators();
},
setNativeProps(props: Object) {
if (this._scrollNodeRef) {
this._scrollNodeRef.setNativeProps(props);
}
},
/**
* Returns a reference to the underlying scroll responder, which supports
* operations like `scrollTo`. All ScrollView-like components should
@@ -67,10 +62,18 @@ const ScrollView = ((createReactClass({
return this._scrollNodeRef;
},
getInnerViewRef(): any {
return this._innerViewRef;
},
getInnerViewNode(): any {
return this._innerViewRef;
},
getNativeScrollRef(): any {
return this._scrollNodeRef;
},
/**
* Scrolls to a given x, y offset, either immediately or with a smooth animation.
* Syntax:
@@ -129,6 +132,7 @@ const ScrollView = ((createReactClass({
stickyHeaderIndices,
pagingEnabled,
/* eslint-disable */
forwardedRef,
keyboardDismissMode,
onScroll,
/* eslint-enable */
@@ -261,12 +265,29 @@ const ScrollView = ((createReactClass({
this.scrollResponderHandleScroll(e);
},
_setInnerViewRef(component) {
this._innerViewRef = component;
_setInnerViewRef(node) {
this._innerViewRef = node;
},
_setScrollNodeRef(component) {
this._scrollNodeRef = component;
_setScrollNodeRef(node) {
this._scrollNodeRef = node;
// ScrollView needs to add more methods to the hostNode in addition to those
// added by `usePlatformMethods`. This is temporarily until an API like
// `ScrollView.scrollTo(hostNode, { x, y })` is added to React Native.
if (node != null) {
node.getScrollResponder = this.getScrollResponder;
node.getInnerViewNode = this.getInnerViewNode;
node.getInnerViewRef = this.getInnerViewRef;
node.getNativeScrollRef = this.getNativeScrollRef;
node.getScrollableNode = this.getScrollableNode;
node.scrollTo = this.scrollTo;
node.scrollToEnd = this.scrollToEnd;
node.flashScrollIndicators = this.flashScrollIndicators;
node.scrollResponderZoomTo = this.scrollResponderZoomTo;
node.scrollResponderScrollNativeHandleToKeyboard = this.scrollResponderScrollNativeHandleToKeyboard;
}
const ref = mergeRefs(this.props.forwardedRef);
ref(node);
}
}): any): React.ComponentType<ScrollViewProps>);
@@ -313,4 +334,10 @@ const styles = StyleSheet.create({
}
});
export default ScrollView;
const ForwardedScrollView = React.forwardRef((props, forwardedRef) => {
return <ScrollView {...props} forwardedRef={forwardedRef} />;
});
ForwardedScrollView.displayName = 'ScrollView';
export default ForwardedScrollView;