react-native-pager-view: [Discussion] setPage should not trigger onPageSelected event

Bug

We are storing and updating the active index of the ViewPager in Redux. When the user swipes to another page the onPageSelected event is correctly triggered and the active index will be updated like that:

<ViewPager
      ref={pager}
      pageMargin={0}
      onPageSelected={({ nativeEvent }) => setActiveIndex(nativeEvent.position)}
      style={[styles.pager, { width }]}
      initialPage={0}
>

But we also want to set the page programatically when the active index is changed by some other component. To achieve this we are using an useEffect hook like this:

useEffect(() => {
    pager.current && pager.current.setPageWithoutAnimation(activeIndex);
}, [activeIndex]);

Unfortunately calling setPageWithoutAnimation triggers another onPageSelected event which updates the active index which triggers the useEffect hook and so on.

We think that there should be no onPageSelected event triggered when setting the page programatically.

Environment info

React native info output:

System:
    OS: macOS Mojave 10.14.6
    CPU: (4) x64 Intel(R) Core(TM) i5-7360U CPU @ 2.30GHz
    Memory: 5.96 GB / 16.00 GB
    Shell: 3.2.57 - /bin/bash
  Binaries:
    Node: 10.16.3 - ~/.nvm/versions/node/v10.16.3/bin/node
    Yarn: 1.19.1 - /usr/local/bin/yarn
    npm: 6.13.1 - ~/.nvm/versions/node/v10.16.3/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  SDKs:
    iOS SDK:
      Platforms: iOS 13.2, DriverKit 19.0, macOS 10.15, tvOS 13.2, watchOS 6.1
    Android SDK:
      API Levels: 21, 22, 23, 24, 25, 26, 27, 28
      Build Tools: 23.0.1, 25.0.2, 25.0.3, 26.0.1, 26.0.2, 26.0.3, 27.0.3, 28.0.1, 28.0.3, 29.0.2
      System Images: android-19 | Google APIs Intel x86 Atom, android-21 | Google APIs Intel x86 Atom_64, android-22 | Intel x86 Atom_64, android-22 | Google APIs Intel x86 Atom_64, android-23 | Google APIs Intel x86 Atom_64, android-26 | Intel x86 Atom_64, android-27 | Google APIs Intel x86 Atom, android-28 | Google APIs Intel x86 Atom
      Android NDK: 20.1.5948944
  IDEs:
    Android Studio: 3.5 AI-191.8026.42.35.6010548
    Xcode: 11.3/11C29 - /usr/bin/xcodebuild
  npmPackages:
    react: 16.9.0 => 16.9.0
    react-native: 0.61.5 => 0.61.5

Library version: 3.3.0

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 11
  • Comments: 25 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Ideally would be good to have react-native-viewpager as controlled component. Instead of initialPage might be better to have page which will control selected page.

@crystalneth not yet. what you describe is exactly our problem. I’ll take a closer look this week and let you know.

I have the same issue. Distinguishing between desiredIndex and currentIndex isn’t really possible, because we can’t distinguish between a user initiated page scroll and programmatically initiated one, at least without some transitioning state variable.

You can see in the code below, if a user taps to change the segmented control index from 0 to 1, then back to 0 before the pager has finished transitioning to 1, then the onPageSelected event will fire with ‘1’ before the user initiated transition back to 0 has started. An infinite loop ensues.

Note that unlike the above example I am using animations, which I do want.

Also note that the SegmentedControlIOS onChange event does not fire when its index is programmatically changed, however the ViewPager event does fire when its index is programmatically change. I believe the former is the correct behavior.

 const viewPager = React.createRef<ViewPager>()
 const [pageIndex, setPageIndex] = useState(0)

  useEffect(() => {
    console.log('useEffect setPage '+pageIndex)
    viewPager.current?.setPage(pageIndex)
  }, [pageIndex]);

...

        <SegmentedControlIOS
          selectedIndex={pageIndex}
          onChange={event => setPageIndex(event.nativeEvent.selectedSegmentIndex)}
        />
        <ViewPager initialPage={pageIndex} ref={viewPager} 
            onPageSelected={e => setPageIndex(e.nativeEvent.position)}
            >
...

What do you think about using a desiredIndex value as the useEffect dependency, so you have no infinite loop?

To be clear, I disagree with removing the onPageSelected event, because in this example, “current” and “desired” page values are not properly separated.