facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
117.23k stars 24.1k forks source link

(Android/FlatList) Fix onMomentumScrollEnd being called multiple times #32696

Open huangkaiyuan opened 2 years ago

huangkaiyuan commented 2 years ago

Description

image

Version

0.64.1

Output of react-native info

image

Steps to reproduce

image

Snack, code example, screenshot, or link to a repository

No response

fanyangxi commented 2 years ago

Observing the same symptom. FYI, the onMomentumScrollBegin was triggered only once, but the onMomentumScrollEnd was trigged around 3~5 times.

image

Arjan-Zuidema commented 2 years ago

Same bug occurs on the ScrollView component

meowcorp commented 2 years ago

+1

anton-patrushev commented 2 years ago

Was able to reproduce it on the 0.66.4 version. Android only. Hermes is enabled (maybe it would be useful). I have faced with that issue on 0.64.2 first time. But it's still reproducible.

rubydeve commented 2 years ago

+1

rubydeve commented 2 years ago

This is how it worked for me

const [canmomentum, setCanMomentum] = useState(false);
<ScrollView
      onScroll={( event ) => {
           setCanMomentum(true)
     }}
     onMomentumScrollEnd={() => {
           if (canmomentum) console.log('onMomentumScrollEnd')
           setCanMomentum(false)
     }}
>
     <Content/>
</ScrollView>
ewnavilae commented 2 years ago

This is how it worked for me

const [canmomentum, setCanMomentum] = useState(false);
<ScrollView
      onScroll={( event ) => {
           setCanMomentum(true)
     }}
     onMomentumScrollEnd={() => {
           if (canmomentum) console.log('onMomentumScrollEnd')
           setCanMomentum(false)
     }}
>
     <Content/>
</ScrollView>

Thanks for this suggestion, I did something similar but with a ref instead to avoid unnecessary state updates and renders.

achiyazigi commented 2 years ago

This is how it worked for me

const [canmomentum, setCanMomentum] = useState(false);
<ScrollView
      onScroll={( event ) => {
           setCanMomentum(true)
     }}
     onMomentumScrollEnd={() => {
           if (canmomentum) console.log('onMomentumScrollEnd')
           setCanMomentum(false)
     }}
>
     <Content/>
</ScrollView>

Thanks for this suggestion, I did something similar but with a ref instead to avoid unnecessary state updates and renders.

I didn't find how to reference the scrolling state from ref. would you agree to share your solution?

asr1191 commented 2 years ago

This is how it worked for me

const [canmomentum, setCanMomentum] = useState(false);
<ScrollView
      onScroll={( event ) => {
           setCanMomentum(true)
     }}
     onMomentumScrollEnd={() => {
           if (canmomentum) console.log('onMomentumScrollEnd')
           setCanMomentum(false)
     }}
>
     <Content/>
</ScrollView>

Thanks for this suggestion, I did something similar but with a ref instead to avoid unnecessary state updates and renders.

I didn't find how to reference the scrolling state from ref. would you agree to share your solution?

Second this, please share thy precious knowledge of the hooks. Saveth me from mine own perils! @ewnavilae I beg of you.

josecarlosrx commented 2 years ago

Solution using useRef:

const canMomentum = React.useRef(false);

const onMomentumScrollBegin = () => {
  canMomentum.current = true;
};

const onMomentumScrollEnd = () => {
  if (canMomentum.current) {
    // console.log('onMomentumScrollEnd');
  }

  canMomentum.current = false;
};

return (
  <ScrollView
    onMomentumScrollBegin={onMomentumScrollBegin}
    onMomentumScrollEnd={onMomentumScrollEnd}
  ></ScrollView>
);
MarceloNascc commented 1 year ago

I'm having the same issue in version 0.68.2

RobbertMerapar commented 1 year ago

Still an issue in version 0.69.6

MichHighlander commented 1 year ago

Still an issue in version 0.70

stingerdallas commented 1 year ago

Any update on this? The workaround doesn't work if using the snapToInterval because its the last event that is correct. I can put in a debounce but it is way too long of a debounce since the onMomentumScrollEnd gets called almost a second apart.

When using snapToInterval, and user drags very little and lets go, it snaps back to the original snap location, but onMomentumScrollEnd gets called twice. once for the temp scrolled location and once for the new location

kirx76 commented 1 year ago

Still an issue in version 0.71.7

wyeo commented 1 year ago

+1

Jlexyc commented 1 year ago

+1

Bowlerr commented 1 year ago

+1

gaearon commented 9 months ago

Looks like this will be fixed by https://github.com/facebook/react-native/pull/32433.

gaearon commented 9 months ago

Note there's a similar problem for onScrollBeginDrag (which gets called twice). I don't know if that PR fixes it.

hichemfantar commented 8 months ago

I'm still having this issue in react-native 0.72.6.

hichemfantar commented 8 months ago

Solution using useRef:

const canMomentum = React.useRef(false);

const onMomentumScrollBegin = () => {
  canMomentum.current = true;
};

const onMomentumScrollEnd = () => {
  if (canMomentum.current) {
    // console.log('onMomentumScrollEnd');
  }

  canMomentum.current = false;
};

return (
  <ScrollView
    onMomentumScrollBegin={onMomentumScrollBegin}
    onMomentumScrollEnd={onMomentumScrollEnd}
  ></ScrollView>
);

This works but the event is called too early, it doesn't wait for the animation to end.

github-actions[bot] commented 2 months ago

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 7 days.

xaiamov commented 1 month ago

It must be fixed in RN 0.74.1 https://github.com/facebook/react-native/commit/06668fcbacd750771f1d53cce829dc55e86f3f3c

Iuriy-Budnikov commented 1 month ago

Not fixed :(

mars-lan commented 1 week ago

Using RN 0.74.3 and can confirm that this issue has been fixed.