Skip to content

Commit eeb3bff

Browse files
committed
Model scrolling to input as a React effect
1 parent b4b6a65 commit eeb3bff

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
export default class RNPickerSelect extends PureComponent {
2127
static contextType = PickerStateContext;
@@ -248,13 +254,19 @@ export default class RNPickerSelect extends PureComponent {
248254

249255
// If TextInput is below picker modal, scroll up
250256
if (textInputBottomY > modalY) {
257+
const scrollView = this.props.scrollViewRef.current;
258+
const scrollViewContentOffsetY = this.props.scrollViewContentOffsetY;
259+
251260
// Wait until the modal animation finishes, so the scrolling is effective when PickerAvoidingView is
252261
// used
253-
setTimeout(() => {
254-
this.props.scrollViewRef.current.scrollTo({
255-
y: textInputBottomY - modalY + 10 + this.props.scrollViewContentOffsetY,
256-
});
257-
}, IOS_MODAL_ANIMATION_DURATION_MS + 50);
262+
this.setState({
263+
scrollToInputEffect: () =>
264+
schedule(() => {
265+
scrollView.scrollTo({
266+
y: textInputBottomY - modalY + 10 + scrollViewContentOffsetY,
267+
});
268+
}, IOS_MODAL_ANIMATION_DURATION_MS + 50),
269+
});
258270
}
259271
});
260272
}
@@ -615,7 +627,7 @@ export default class RNPickerSelect extends PureComponent {
615627
);
616628
}
617629

618-
render() {
630+
renderForPlatform() {
619631
const { children, useNativeAndroidPickerStyle } = this.props;
620632

621633
if (Platform.OS === 'ios') {
@@ -632,6 +644,14 @@ export default class RNPickerSelect extends PureComponent {
632644

633645
return this.renderAndroidNativePickerStyle();
634646
}
647+
648+
render() {
649+
return (
650+
<EffectRunner effect={this.state.scrollToInputEffect}>
651+
{this.renderForPlatform()}
652+
</EffectRunner>
653+
);
654+
}
635655
}
636656

637657
export { defaultStyles, PickerStateProvider, PickerAvoidingView };

0 commit comments

Comments
 (0)