Skip to content

Commit e84e5f7

Browse files
committed
Model scrolling to input as a React effect
1 parent 06a814a commit e84e5f7

File tree

3 files changed

+32
-12
lines changed

3 files changed

+32
-12
lines changed

src/PickerAvoidingView/index.ios.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,7 @@ import React from 'react';
22
import { StyleSheet, View } from 'react-native';
33
import { PickerStateContext } from '../PickerStateProvider';
44
import { IOS_MODAL_ANIMATION_DURATION_MS, IOS_MODAL_HEIGHT } from '../constants';
5-
6-
function schedule(callback, timeout) {
7-
const handle = setTimeout(callback, timeout);
8-
return () => clearTimeout(handle);
9-
}
5+
import { schedule } from './utils';
106

117
/**
128
* PickerAvoidingView is a React component that adjusts the view layout to avoid

src/PickerAvoidingView/utils.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export function schedule(callback, timeout) {
2+
const handle = setTimeout(callback, timeout);
3+
return () => clearTimeout(handle);
4+
}

src/index.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { PureComponent } from 'react';
1+
import React, { PureComponent, useEffect } from 'react';
22
import {
33
Dimensions,
44
Keyboard,
@@ -16,6 +16,12 @@ import { defaultStyles } from './styles';
1616
import { PickerAvoidingView } from './PickerAvoidingView';
1717
import { PickerStateContext, PickerStateProvider } from './PickerStateProvider';
1818
import { IOS_MODAL_ANIMATION_DURATION_MS, IOS_MODAL_HEIGHT } from './constants';
19+
import { schedule } from './PickerAvoidingView/utils';
20+
21+
function EffectRunner({ effect, children }) {
22+
useEffect(effect ?? (() => {}), [effect]);
23+
return children;
24+
}
1925

2026
const preserveSpaces = (label) => {
2127
return label.replace(/ /g, '\u00a0');
@@ -253,13 +259,19 @@ export default class RNPickerSelect extends PureComponent {
253259

254260
// If TextInput is below picker modal, scroll up
255261
if (textInputBottomY > modalY) {
262+
const scrollView = this.props.scrollViewRef.current;
263+
const scrollViewContentOffsetY = this.props.scrollViewContentOffsetY;
264+
256265
// Wait until the modal animation finishes, so the scrolling is effective when PickerAvoidingView is
257266
// used
258-
setTimeout(() => {
259-
this.props.scrollViewRef.current.scrollTo({
260-
y: textInputBottomY - modalY + 10 + this.props.scrollViewContentOffsetY,
261-
});
262-
}, IOS_MODAL_ANIMATION_DURATION_MS + 50);
267+
this.setState({
268+
scrollToInputEffect: () =>
269+
schedule(() => {
270+
scrollView.scrollTo({
271+
y: textInputBottomY - modalY + 10 + scrollViewContentOffsetY,
272+
});
273+
}, IOS_MODAL_ANIMATION_DURATION_MS + 50),
274+
});
263275
}
264276
});
265277
}
@@ -634,7 +646,7 @@ export default class RNPickerSelect extends PureComponent {
634646
);
635647
}
636648

637-
render() {
649+
renderForPlatform() {
638650
const { children, useNativeAndroidPickerStyle } = this.props;
639651

640652
if (Platform.OS === 'ios') {
@@ -651,6 +663,14 @@ export default class RNPickerSelect extends PureComponent {
651663

652664
return this.renderAndroidNativePickerStyle();
653665
}
666+
667+
render() {
668+
return (
669+
<EffectRunner effect={this.state.scrollToInputEffect}>
670+
{this.renderForPlatform()}
671+
</EffectRunner>
672+
);
673+
}
654674
}
655675

656676
export { defaultStyles, PickerStateProvider, PickerAvoidingView };

0 commit comments

Comments
 (0)