Skip to content

ScrollView, HorizontalScrollView: do not ignore nullcontentOffset prop - #28760

Closed
vonovak wants to merge 1 commit into
react:masterfrom
vonovak:consider-null-contentOffset
Closed

ScrollView, HorizontalScrollView: do not ignore nullcontentOffset prop#28760
vonovak wants to merge 1 commit into
react:masterfrom
vonovak:consider-null-contentOffset

Conversation

@vonovak

@vonovakvonovak commented Apr 26, 2020

Copy link
Copy Markdown
Contributor

Summary

motivation: I was just checking out 30cc158
and noticed that the commit, I believe, is missing logic for when contentOffset is actually null.

That is, consider you render ScrollView with contentOffset { x: 0, y: 100 } and then change that to null / undefined. I'd expect the content offset to invalidate (set to 0 - hope that's the default).

Changelog

[Android] [Fixed] - ScrollView, HorizontalScrollView: do not ignore nullcontentOffset prop

Test Plan

Tested locally within RNTester

code

constEx=()=>{let_scrollView: ?React.ElementRef<typeofScrollView>=React.useRef(null);const[offset,setOffset]=React.useState({x: 0,y: 20});setTimeout(()=>{setOffset(undefined);},2000);return(<View><ScrollViewref={_scrollView}automaticallyAdjustContentInsets={false}onScroll={()=>{console.log('onScroll!');}}contentOffset={offset}scrollEventThrottle={200}style={styles.scrollView}>{ITEMS.map(createItemRow)}</ScrollView><Buttonlabel="Scroll to top"onPress={()=>{nullthrows(_scrollView.current).scrollTo({y: 0});}}/><Buttonlabel="Scroll to bottom"onPress={()=>{nullthrows(_scrollView.current).scrollToEnd({animated: true});}}/><Buttonlabel="Flash scroll indicators"onPress={()=>{nullthrows(_scrollView.current).flashScrollIndicators();}}/></View>);};

@facebook-github-botfacebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 26, 2020
@analysis-bot

Copy link
Copy Markdown
PlatformEngineArchSize (bytes)Diff
androidhermesarm64-v8a6,743,51540
androidhermesarmeabi-v7a6,408,49147
androidhermesx867,125,41740
androidhermesx86_647,015,80155
androidjscarm64-v8a8,912,03527
androidjscarmeabi-v7a8,569,42444
androidjscx868,737,07855
androidjscx86_649,313,09657

Base commit: 30cc158

@analysis-bot

analysis-bot commented Apr 26, 2020

Copy link
Copy Markdown
PlatformEngineArchSize (bytes)Diff
ios-universal809,5680

Base commit: 30cc158

@JoshuaGross

Copy link
Copy Markdown
Contributor

Hi @vonovak - what does iOS do, will it reset to 0,0 if you pass in null from JS?

@JoshuaGross

Copy link
Copy Markdown
Contributor

Depending on what the behavior is for iOS, I actually don't think this is the correct thing to do. Passing in null equivalent to saying "delete this prop from the native side". I would intuitively expect that to actually cause no visible changes. If the engineer wants to reset the position to 0,0 I think those coordinates should be passed in directly.

Treating null as 0,0 also prevents us from doing the following in a typesafe manner: "Pass in coordinates if they're present, otherwise pass along no value, which will cause a noop". The only other way is to pass in condition ? { contentOffset: data } : {} and spread that into the element, which Flow and probably TypeScript don't like.

@vonovak

vonovak commented May 27, 2020

Copy link
Copy Markdown
ContributorAuthor

@JoshuaGross Let me start with a premise that rendering the following should always render the same result (I hope that makes sense 😃 ). To give an example, imagine we render this ScrollView, then re-render it with different props and the last render looks again like this:

<ScrollView
contentOffset={undefined}>
{ITEMS.map(createItemRow)}
</ScrollView>

now if we take the example code I gave (I just changed null from the original post to undefined)

const Ex = () => {
const [offset, setOffset] = React.useState({x: 0, y: 20});
setTimeout(() => {
setOffset(undefined);
}, 2000);
return (
<View>
<ScrollView
contentOffset={offset}
style={styles.scrollView}>
{ITEMS.map(createItemRow)}
</ScrollView>
</View>
);
};

first, we start with contentOffset={{x: 0, y: 20}}, then we re-render with contentOffset={undefined}. At this point, https://github.com/facebook/react-native/blob/a38166cfa43472035c403ef9c6aee5a0d13a8a19/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollViewManager.java#L305

will be called with null because we effectively removed the contentOffset prop. With code from master, nothing will happen. That breaks the initial premise which is why I think the behavior is incorrect. I think if developer wants to control the offset "outside of react" they should use imperative scrollTo* methods.

edit: iOS will scroll to top for both setOffset(undefined) and setOffset(null). So this should unify the behavior.

It's rather interesting on iOS, I don't know the internals, but if I pass contentOffset={undefined}, that undefined is treated as nil in ObjectiveC and is transformed into CGPoint (x = 0, y = 0).

@vonovak

Copy link
Copy Markdown
ContributorAuthor

@JoshuaGross would you please re-review? Thanks!

@vonovak

Copy link
Copy Markdown
ContributorAuthor

@JoshuaGross sorry to ping you again, would you please take a look? The PR unifies behavior between ios and android. Thank you!

@JoshuaGross

Copy link
Copy Markdown
Contributor

Thanks for clarifying that this will unify behavior! I will test a bit internally.

@facebook-github-botfacebook-github-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JoshuaGross has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@react-native-bot

Copy link
Copy Markdown
Collaborator

This pull request was successfully merged by @vonovak in 9e85b7a.

When will my fix make it into a release? | Upcoming Releases

@react-native-botreact-native-bot added the Merged This PR has been merged. label Jun 30, 2020
kelset pushed a commit that referenced this pull request Nov 27, 2020
…` prop (#28760)
Summary:
motivation: I was just checking out 30cc158
and noticed that the commit, I believe, is missing logic for when `contentOffset` is actually `null`.
That is, consider you render `ScrollView` with `contentOffset` { x: 0, y: 100 } and then change that to null / undefined. I'd expect the content offset to invalidate (set to 0 - hope that's the default).
## Changelog
<!-- Help reviewers and the release process by writing your own changelog entry. For an example, see:
https://github.com/facebook/react-native/wiki/Changelog
-->
[Android] [Fixed] - ScrollView, HorizontalScrollView: do not ignore `null` `contentOffset` prop
Pull Request resolved: #28760
Test Plan:
Tested locally within RNTester
<details><summary>code</summary>
<p>
```js
const Ex = () => {
let _scrollView: ?React.ElementRef<typeof ScrollView> = React.useRef(null);
const [offset, setOffset] = React.useState({x: 0, y: 20});
setTimeout(() => {
setOffset(undefined);
}, 2000);
return (
<View>
<ScrollView
ref={_scrollView}
automaticallyAdjustContentInsets={false}
onScroll={() => {
console.log('onScroll!');
}}
contentOffset={offset}
scrollEventThrottle={200}
style={styles.scrollView}>
{ITEMS.map(createItemRow)}
</ScrollView>
<Button
label="Scroll to top"
onPress={() => {
nullthrows(_scrollView.current).scrollTo({y: 0});
}}
/>
<Button
label="Scroll to bottom"
onPress={() => {
nullthrows(_scrollView.current).scrollToEnd({animated: true});
}}
/>
<Button
label="Flash scroll indicators"
onPress={() => {
nullthrows(_scrollView.current).flashScrollIndicators();
}}
/>
</View>
);
};
```
</p>
</details>
Reviewed By: shergin
Differential Revision: D22298676
Pulled By: JoshuaGross
fbshipit-source-id: e411ba4c8a276908e354d59085d164a38ae253c0
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

BugCLA SignedThis label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.MergedThis PR has been merged.Platform: AndroidAndroid applications.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@vonovak@analysis-bot@JoshuaGross@react-native-bot@facebook-github-bot