Skip to content

Slow rendering performance on scroll #139

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

Closed
jonahallibone opened this issue May 20, 2021 · 17 comments
Closed

Slow rendering performance on scroll #139

jonahallibone opened this issue May 20, 2021 · 17 comments

Comments

@jonahallibone
Copy link
Contributor

jonahallibone commented May 20, 2021

I've been using the last few versions of react-virtual and i love the hook based api. However, I am getting some poor performance on row rendering. It's the classic empty and then flash to content on scroll. I thought perhaps this could be an issue with emotion (which i use for styling), however after stripping away all the emotion styles on the rows, it still performs quite slowly. I have broken everything up quite a bit and made sure that rerenders are localized only to components which have state changes. I've also played around with the overscan quite a bit, and can't seem to find a setting which renders smoothly. For the record, I am also using this with react-table. I am wondering if I am using the keys incorrectly somehow, or if this is just expected behavior. Below I've included some code snippets and a video:

virtualized-table-row.js

import { Flex } from "@chakra-ui/react";
import { Fragment } from "react";

const VirtualizedTableRow = ({ virtualRow, row }) => (
  <Flex
    ref={virtualRow.measureRef}
    h={`${virtualRow.size}px`}
    bg={row.isSelected ? "indigo.50" : "white"}
    _hover={{
      bg: row.isSelected ? "indigo.100" : "gray.100",
    }}
    position="absolute"
    w="100%"
    top="0"
    left="0"
    alignItems="center"
    style={{ transform: `translateY(${virtualRow.start}px)` }}
    minWidth="min-content"
    zIndex="1"
    role="group"
  >
    {row.cells.map((cell) => (
      <Fragment key={cell.getCellProps().key}>{cell.render("Cell")}</Fragment>
    ))}
  </Flex>
);

export default VirtualizedTableRow;

virtualized-table-body.js

/* eslint-disable react/jsx-props-no-spreading */
import { Box, Center, Spinner } from "@chakra-ui/react";
import { useCallback, useEffect } from "react";
import { useVirtual } from "react-virtual";
import VirtualizedTableRow from "./virtualized-table-row";
import VirtualizedTableRows from "./virtualized-table-rows";

const VirtualizedTableBody = ({
  getTableBodyProps,
  prepareRow,
  rows,
  memoizedData,
  container,
  getNextPage = null,
  page,
  scrollYProgress,
  isLoading,
}) => {
  const rowVirtualizer = useVirtual({
    size: rows.length || 5,
    parentRef: container,
    estimateSize: useCallback(() => 42, []),
    overscan: 100,
    keyExtractor: (index) => rows[index]?.id,
    paddingEnd: getNextPage && page > 1 ? 50 : 0,
  });

  console.log("rerender");

  useEffect(
    () =>
      scrollYProgress.onChange((latest) => {
        if (latest === 1 && !isLoading) {
          getNextPage();
        }
      }),
    [getNextPage, isLoading, scrollYProgress]
  );

  return (
    <Box
      {...getTableBodyProps()}
      minWidth="min-content"
      h={`${rowVirtualizer.totalSize}px`}
      position="relative"
    >
      <VirtualizedTableRows
        rowVirtualizer={rowVirtualizer}
        rows={rows}
        memoizedData={memoizedData}
        prepareRow={prepareRow}
      />

      <Box position="absolute" h="50px" w="100%" bg="white" bottom="0">
        <Center h="100%">{isLoading && page > 1 && <Spinner />}</Center>
      </Box>
    </Box>
  );
};

export default VirtualizedTableBody;
Kapture.2021-05-20.at.12.14.02.mp4

Thanks again!

@jonahallibone jonahallibone changed the title Slow performance on scroll Slow rendering performance on scroll. May 20, 2021
@jonahallibone jonahallibone changed the title Slow rendering performance on scroll. Slow rendering performance on scroll May 20, 2021
@jsbeckr
Copy link

jsbeckr commented Aug 16, 2021

Probably not relevant anymore, but I had the same issue with Chakra Table.

When I switched out every Chakra component to a 'normal' html element it rendered the rows much much faster.

@jonahallibone
Copy link
Contributor Author

@jsbeckr so we mananged to speed up the rendering quite a bit by changing some chakra setting on the popovers we used. Essentially they were being rerendered on each scroll, and we were able to prevent that. I still do have some concerns generally about rows with a lot of information because as of now each cell in a row still gets rerendered by default, even if remaining in view. I did memoize quite a bit of the table to enhance the performance out of the box.

@tannerlinsley
Copy link
Collaborator

Yeah, this has a lot to do with the components you are rendering and how many you are rendering. We'll keep improving the perf in react-virtual as much as we can, but can't control everything :)

Let us know if you run into more perf problems!

@livevsonline
Copy link

@jsbeckr This was spot on for me... Chakra made everything crazy slow. I guess I won't the using a components library after all, neither MUI, Chakra nor the one from Microsoft... at least not for tables

@ssp7
Copy link

ssp7 commented Jan 2, 2024

Does this slow row rendering issue apply to mui Components? For me, List items consist of the Mui timeline component with a bunch of nested Components (TimelineContent, Typography, Box, etc...)

@jagodin
Copy link

jagodin commented May 4, 2024

Does this slow row rendering issue apply to mui Components? For me, List items consist of the Mui timeline component with a bunch of nested Components (TimelineContent, Typography, Box, etc...)

It appears so. I'm using a bunch of MUI components in my table (Checkbox, InputBase, Button, Typography, etc.) and can't figure out a way to fix the flickering without removing the MUI components.

@justasdev
Copy link

I'm experiencing the same. Trying to migrate our custom table component from react virtuoso to tanstack virtual, because tanstack virtual is much more flexible. Performance was perfect in react virtuoso, but with tanstack virtual it is something like in your video.

@piecyk
Copy link
Collaborator

piecyk commented May 15, 2024

I'm experiencing the same. Trying to migrate our custom table component from react virtuoso to tanstack virtual, because tanstack virtual is much more flexible. Performance was perfect in react virtuoso, but with tanstack virtual it is something like in your video.

@justasdev tanstack virtual is not doing any performance optimization while scrolling, so on every scroll everything is re-rendered, hard to say what is happening without reading the code, but try to pin point what is slow.

@Torsteinws
Copy link

I have the exact same problem. Any guidance on how to handle rendering of MUI components?

Using pure html solves the problem, but that is unfortunately not a viable option as my app is deeply committed to MUI.

@ssp7
Copy link

ssp7 commented Oct 16, 2024

Anyone having issues with fixing their scroll flickering or lagging this has done wonders for me #619 (comment) . I should mention that this fix is for items have different dimensions i.e. dynamic scrolling where dimensions get generated while scrolling.

@Torsteinws
Copy link

Torsteinws commented Oct 17, 2024

I solved the issue by wrapping every item in React.memo. This seems to mitigate unnecessary rerendering on scroll.

Here is a full example for dynamically sized items:

function VirtualList({ data }: { data: ListItemType[] }) { r
    
    const virtualizer = useWindowVirtualizer({
        count: data.length,
        estimateSize: (i) => 1000, 
        overscan: 5,
    })

    const virtualItems = virtualizer.getVirtualItems()
      
    return (
        <div style={{
            position: 'relative',
            height: `${virtualizer.getTotalSize()}px`,
        }}>
            <div style={{
                position: 'absolute',
                top: 0,
                left: 0,
                width: '100%',
                transform: `translateY(${virtualItems[0]?.start ?? 0}px)`,
            }}>
                {virtualItems.map( (virtualItem) => ( 
                    <div key={virtualItem.key} 
                        ref={virtualizer.measureElement}
                        data-index={virtualItem.index}
                    >
                        <MemoizedListItem data={data[virtualItem.index]} />
                    </div>
                ))}
            </div>
        </div>
    )
}

const MemoizedListItem = React.memo(({ data }: { data: ListItemType[] }) => { 
    return (
        <div style={{paddingBottom: "30px"}}>
            <ListItem data={data} />
        </div>
    )
})

@piecyk
Copy link
Collaborator

piecyk commented Oct 17, 2024

@Torsteinws Good to hear that wrapping items in React.memo helped with the performance! Optimizing virtualized lists is definitely not a trivial task. Performance can be influenced by many factors beyond just code, like hardware capabilities and how quickly the browser can render elements. It's all about finding the right balance for your specific use case, but every bit of optimization helps overall.

Let me know if you come across any other findings!

@Torsteinws
Copy link

Torsteinws commented Oct 17, 2024

It seems to me that the virtualizer is triggering a bunch of "unnecessary" rerenderings when scrolling. In my case, it caused me to feel a fair amount of friction when getting started with the library. Perhaps this is something the TanStack Virtual team can consider to address somehow?

If it is difficult to optimize the library in a way that fits all use cases, maybe it is possible to expose an option that somehow let you limit the amount of rerenders? If not, it would go a long way to just document best practices for having a performant virtual list.

Anyway, kudos for making a great api. The virtualizer feels ergonomic to use.

@piecyk
Copy link
Collaborator

piecyk commented Oct 18, 2024

It seems to me that the virtualizer is triggering a bunch of "unnecessary" rerenderings when scrolling.

@Torsteinws This should be fixed alredy via #806 if you can pin point where it happens in latest version, let's fix it.

@Torsteinws
Copy link

Torsteinws commented Oct 18, 2024

I have been using version 3.10.8, so I should have this fix already.

I wonder if all items in a list are rerendered whenever virtualizer.getVirtualItems() changes? If that is true, I can see why it can be expensive to scroll – especially if the items are a bit heavy to render or if the user scrolls a bit fast.

@piecyk
Copy link
Collaborator

piecyk commented Oct 23, 2024

I wonder if all items in a list are rerendered whenever virtualizer.getVirtualItems() changes? If that is true, I can see why it can be expensive to scroll

No, we only re-render when range is changing or item position/size changes, you can easy test that by few of console logs.

@timmydoza
Copy link

I was facing a similar issue - where scrolling a virtualized list was not 100% buttery smooth. Turns out that MacOS will cap the FPS of some things when your battery is low. Before trying anything else, just make sure your computer is plugged in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants