javascriptperformancereactjsreact-virtualizedtether

Performance issue with react-tether inside item in a react-virtualized list


Description

I have a potentially long list of items rendered in a react-virtualized VirtualScroll.
Each item (row) in the list has a fairly large amount of elements, one of which opens a context menu. I'm trying to use react-tether to render that menu on the HTML body (so that it's not hidden when the item is at the bottom/top of the scrollable list) and keep the menu 'stuck' to my item while the user scrolls through the list.
My problem is that there is a noticeable lag in updating the position of the tethered menu.

Some of the steps I've taken so far:

  1. Rendered a simple list, without VirtualScroll. The tethered menu was rendered smoothly, no noticeable jank. That's how I concluded that the problem is with react-virtualized
  2. Simplified my rowRenderer down to only the menu trigger, as recommended here.
  3. Implemented shouldComponentUpdate in the row component. This has improved the perceived performance greatly, reduced the delay greatly but it is still noticeable.
  4. Checked Chrome devtools' timeline. I see reflows triggered by both Grid.js and tether.js.

Library Versions:

Working Demo

https://plnkr.co/edit/f7OhCoCXkDsWbyjxhR3f

Screenshot:

screenshot


Solution

  • FYI that Plnkr was broken. It was including the wrong styles version (8.x instead of 7.x).

    After fixing that, the visual "lag" I see is not something I know how to fix with version 7.x. The problem is that the browser manages the scroll in a different thread (so that JS doesn't block it and cause the UI to feel unresponsive). Normally this isn't noticeable because all of the UI is being scrolled together, however in this case- your modal is absolutely positioned, by JS, and so it sometimes lags behind the browser's scroll position.

    That being said, upgrading to version 8 gives you an alternative to the portal approach, which does fix the issue, as shown in this updated plunk: https://plnkr.co/edit/NESPMzDz22JjwFVthve4?p=preview

    They key is this:

    render() {
      const { menuOpen } = this.state;
      const { index, style } = this.props;
    
      // Make sure open cells are on a higher z-index than others
      if (menuOpen) {
        style.zIndex = 2;
      }
    
      return (
        <div
          className="row"
          style={style}
        >
          {list[index]}
    
          {/* Render your button OR menu item here */}
        </div>
      );
    }