-
Notifications
You must be signed in to change notification settings - Fork 10
Implement PickerAvoidingView and PickerStateProvider #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
What's still to be done: creating a symmetric PR to |
683fd45
to
ab0950d
Compare
@Julesssss please assign yourself as a reviewer |
ab0950d
to
039abcb
Compare
039abcb
to
db69948
Compare
index.ios.js:
index.js:
@cubuspl42 this is my suggestion |
If we animate like KeyboardAvoidingView, that would be a great bonus point. Simulator.Screen.Recording.-.iPhone.14.Pro.Max.-.2023-04-13.at.12.55.59.mp4 |
Please note that the problem is of a bit different nature. Resizing the height of the bottom space does not scroll or move the content inside the |
I think you didn't get my point. It's not related to scroll. |
@cubuspl42 I opened slack discussion to move things faster. Let's discuss there for further comments https://expensify.slack.com/archives/C01GTK53T8Q/p1681386144253199 |
db69948
to
ae32675
Compare
ae32675
to
eeb3bff
Compare
@cubuspl42 please fix conflict and point correct hash on app PR |
eeb3bff
to
e84e5f7
Compare
src/index.js
Outdated
|
||
render() { | ||
return ( | ||
<EffectRunner effect={this.state.scrollToInputEffect}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any other way to avoid this wrapper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hooks can't be used in class components. I used useEffect
hook, indirectly via a helper functional component, to ensure that no effect is run after our component gets unmounted. Do you think this should be done manually in the component lifecycle methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think this should be done manually in the component lifecycle methods?
yes, we should try to avoid complex code as possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
useEffect
logic, implemented using component lifecycle methods, looks more or less like this:
componentDidMount() {
this.performEffect();
}
componentDidUpdate(prevProps) {
// check if dependencies changed
if (/* dependencies changed */) {
// Clean up the previous effect before applying the new one
if (this.cleanupEffect) {
this.cleanupEffect();
}
this.performEffect();
}
}
componentWillUnmount() {
if (this.cleanupEffect) {
this.cleanupEffect();
}
}
performEffect() {
// effect code here
// store the cleanup function as an instance variable
this.cleanupEffect = () => {
// cleanup/cancelling code here
};
}
Is that the simple version?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, maybe I'm missing some simpler options. I do agree that things should be as simple as possible. Would you share what is on your mind? You can see the problem we're solving. How would you approach that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you already try onModalShow
callback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By "there's no need for schedule
", you're saying that you wouldn't cancel this timeout in any circumstances, right? So you would guess that it wouldn't cause any trouble if it was run after the picker component is unmounted, right?
I'm not against it, I'm just trying to ensure that I read your words as you mean them. I haven't even tested such edge case, as it also wouldn't be very easy to test. It's just a good pattern to cancel all cancellable side effects when the component which "owns" them is unmounted, destroyed, or something like that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you wouldn't cancel this timeout in any circumstances
I asked: "Isn't that enough to clearTimout on componentWillUnmount?"
Also did you try onModalShow
callback?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked: "Isn't that enough to clearTimout on componentWillUnmount?"
Sorry, I missed that part. It's not really the same, as useEffect
also cancels the effect when the dependencies change. But maybe that part could be dropped.
I'm just investigating the possibility of using onShow
function. onModalShow
is a method from a library that react-native-picker-select
doesn't use. It looks promising and I believe that it's a very good suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0xmiroslav I replaced the time-based approach with onShow
, like you suggested! I changed the name/contract of the flag stored in PickerStateContext
, but that's semantics. I think that the whole solution actually simplified.
a3066df
to
6c340fd
Compare
@@ -245,7 +246,8 @@ export default class RNPickerSelect extends PureComponent { | |||
// If TextInput is below picker modal, scroll up | |||
if (textInputBottomY > modalY) { | |||
this.props.scrollViewRef.current.scrollTo({ | |||
y: textInputBottomY - modalY + this.props.scrollViewContentOffsetY, | |||
// Add 10 pixels for a more visually pleasant effect | |||
y: textInputBottomY + 10 - modalY + this.props.scrollViewContentOffsetY, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you share 2 videos to clarify difference?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Relevant line in Expensify: https://github.com/Expensify/App/blob/main/src/components/Form.js#L357
6c340fd
to
086923c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Checklist will be done in app PR.
For now, please point latest commit hash in app PR.
@Julesssss will be back from OOO tomorrow and after this PR merged, we can simply update commit hash to merged one.
086923c
to
5672096
Compare
...which provide tools for ensuring that a picker is not covered by its own modal on iOS. Call scrollToInput() only on iOS, basing on an observation that this method was designed to handle iOS modal specifically.
5672096
to
5dc57c3
Compare
Code additions/edits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, just curious if the hardcoded height is suitable for iPad devices.
@Julesssss Portrait mode exactly matches hardcoded height but landscape mode height is smaller. So 3 options here to go ahead
I tested on iPad mini 6th generation NOTE: Currently team doesn't really care about iPad responsive. Screen.Recording.2023-04-25.at.12.22.10.PM.mov |
@Julesssss @0xmiroslav It's a good catch! Still, I think that this PR doesn't really introduce any regression in the discussed matter. The constant used for the modal was already present in the source code. Taking into consideration that @0xmiroslav is saying that even the sign in page doesn't support iPad properly, and iPad doesn't get first-class support, I think we don't have to improve the modal measurements, as the effect on iPad is not that bad. |
Yeah this looks fine, so I agree we shouldn't care about this for now 👍 |
...to the one merged in Expensify/react-native-picker-select#8
if (this.context) { | ||
this.context.setIsModalShown(true); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we show the Modal, onShow
callback will be called and set modal as shown (isModalShown=true
). Now if we unmount that modal (while it's shown) the state of the modal being shown won't be reset accordingly - state is stuck at true
. We should have cleared the state on componentWillUnmount
. (Coming from Expensify/App#23044)
...which provide tools for ensuring that a picker is not covered by its own modal on iOS.
I'm still testing these changes, but they are ready to be reviewed, as I don't expect any major changes in the approach for now.