Skip to content
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

Calling scrollToItem() or scrollTo() triggers onScroll (callback) event twice, with inconsistent scrollUpdateWasRequestedProperty #228

Open
tomasro27 opened this issue Apr 29, 2019 · 5 comments · May be fixed by #602
Labels
😭 bug Something isn't working 👋 help wanted Extra attention is needed

Comments

@tomasro27
Copy link

Repro steps:

  1. create a callback function, and pass it as a prop for onScroll to VariableSizeGrid component.
  2. Call scrollToItem() or scrollTo()
  3. Callback function gets called twice (in most cases.. see details below)
  • First time, scrollUpdateWasRequestedProperty is true.
  • Second time, scrollUpdateWasRequestedProperty is false.

Here is a sandbox with the repro:
https://codesandbox.io/s/4lq8xqwn14

  1. Press scrollToDecimalPX button (using scrollTo with fractional part on PX value (500.34))
  2. Check the console logs, and notice how the first one is true, and the second one is false.

Additional findings:

  • Fractional value as an argument to scrollTop property in scrollTo function always repro (two calls to onScroll callback).
  • Integer value as an argument to scrollTop property in scrollTo function makes two calls to onScroll but at least one out of ten times, it only calls it once.
  • ScrollToItem with align: auto or align: start always calls onScroll once (as expected).
  • ScrollToItem with align: end or align: center always calls onScroll twice.
  • ScrollToItem with align: smart calls onScroll once or twice.

Seems like the position in which we are scrolling to has an important role into whether onScroll gets called twice or once.

For context, I am trying to sync react-window VariableSizeGrid with another element B. I am trying to ignore the onScroll callback, but can't achieve that because of this.

@bvaughn bvaughn added 👋 help wanted Extra attention is needed 😭 bug Something isn't working labels May 28, 2019
@bvaughn
Copy link
Owner

bvaughn commented May 28, 2019

I don't think I have the time to look into this. If you'd like to submit a PR with unit tests I'll be happy to review and consider it.

@telaoumatenyanis
Copy link

telaoumatenyanis commented Jun 30, 2019

Hey @bvaughn.
I gave a look at this issue, here is what seems to happen:

The property scrollTop was originally a long and was changed into a double. I know chrome implemented the change, but I don't know if firefox did. I can't get a decimal scroll on firefox 67 (according to MDN, decimal happens on systems using display scaling).

So for Firefox, as the value seems to be rounded down, the following check fail:

if (
prevState.scrollLeft === scrollLeft &&
prevState.scrollTop === scrollTop
) {

The rounded value is different from the double one. It triggers a setState and thus call the callback.
This means that an integer scrollTo will work fine, but a decimal scrollTo will trigger the callback multiple time.

On Chrome it is a bit different, if you zoom, you will be able to have decimal scrollTop. However, there seems to be a small offset between the scrollTop and the scroll requested when the check is performed, thus it returns false and triggers a change of the state:

Screenshot 2019-06-30 at 04 07 28

@bvaughn
Copy link
Owner

bvaughn commented Jun 30, 2019

Thanks for sharing your findings, @telaoumatenyanis. Seems like some rounding may be necessary then. PRs welcome for this.

@anvlkv
Copy link

anvlkv commented Mar 6, 2021

Seems I have somewhat similar issue in firefox, but when using FixedSizeList. Can I help this somehow?

@aaronbabaron
Copy link

FYI I have a workaround that seems to work in my application. I created an OuterElementType that intercepts the scroll event and rounds all values required by react-windows scroll handler before passing it to said scroll handler. This seems to work well enough for my application, but not sure how testing would go to make sure this works everywhere.

const Outer = React.forwardRef<
  any,
  React.HTMLProps<HTMLElement> & {
    componentType?: ReactElementType;
  }
>(({ onScroll, componentType, ...props }, ref) => {
  const Component = componentType || 'div';

  const handleScroll = (scrollEvent: React.MouseEvent<any>) => {
    // Properties required by inner scroll handler
    // https://github.com/bvaughn/react-window/blob/master/src/createGridComponent.js#L731
    const propertiesToRound = [
      'clientHeight',
      'clientWidth',
      'scrollLeft',
      'scrollTop',
      'scrollHeight',
      'scrollWidth'
    ];
    
    // Passing a fake event with values rounded in order to avoid having multiple scroll events fired if browser is zoomed 
    const fakeEvent: any = {
      currentTarget: {} as any
    };

    for (const property of propertiesToRound) {
      fakeEvent.currentTarget[property] = Math.floor(
        scrollEvent.currentTarget[property]
      );
    }

    onScroll?.(fakeEvent);
  };

  return <Component {...props} onScroll={handleScroll} ref={ref}></Component>;
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
😭 bug Something isn't working 👋 help wanted Extra attention is needed
5 participants