TL:DR; Show me the code! https://github.com/mattcolman/react-progressive-list
The DOM is typically slow at rendering long lists. It’s a common problem in web development today, especially on mobile web.
Enter the virtualized list!
A virtualized list works by only rendering rows that are visible on screen, and ignoring the rest. react-virtualized is the big one in the react world. It’s a great package, and if you have an infinite loading list I’d say it’s the best solution (unless you make your own virtualized list like Twitter Lite).
react-virtualized tracks scrolling of the view, mounting and unmounting rows on the fly. Typically scrolling the list (even when swiping really fast) you would expect to only mount 1–2 new rows each frame, which shouldn’t exceed your ~16ms quota to achieve a smooth scroll.
What if you have a complex Row component that takes 10ms to render? And what if each row is quite short in height? That means when you scroll really fast react-virtualized will try to mount and unmount maybe 6 rows each frame, and if each row takes 10ms to render then we’re at 60ms for the frame!
The react-virtualized examples are pretty basic, light-weight examples so they run fast. But even when I convert the rows from divs to React Components and then measure the performance using the
/?react_perf suffix, we can straight away see some long frames. In this example we see 7 new rows mounting causing the frame to take 48.5ms. This is in dev mode so it’s much slower, but again, the example is very basic.
There are so many use cases for long lists on the web, so it’s best to describe the actual reason that react-virtualized didn’t work for me.
I had a list of ~800 rows. Each row was complex with many nested components and took ~8ms to render. The initial render therefore took 800*8ms = over 6 seconds. That is a long time for the ui to be blocked!
When I implemented react-virtualized I was very excited to see the initial render drop dramatically to about 60ms. Yay! However, when scrolling fast on mobile it was a jank fest! My initial thought was to try optimising my Row component to render much faster, but in the real world, we don’t always have the luxury of time. Trying to optimise an 8ms render down to 2ms could take a week or may not even be possible.
react-progressive-list is very simple. It renders a portion of your list, then when you scroll to the end of that portion it renders more of the list. The result is this:
- Fast initial render. You decide how many rows to render.
- Fast scrolling. Modern browsers perform great optimisations on scrolling. You’ll find that once a long list has finished its initial render, scrolling that list is actually very smooth.
- Make jank feel like content loading. We haven’t completely eliminated jank from this list. When you scroll to the end of the list you may decide to render another chunk of 40 rows. This could take a while, but as users we are used to seeing loading animations and are happy to wait a few seconds for new content to arrive. React Fiber will make sure the spinner keeps spinning while rendering the new rows.
On browsers that support
requestIdleCallbacknew rows are also rendered when the browser is idle.
The best part is it should take about 10 minutes to implement react-progressive-list into any existing list. TBH I’ve tried to use react-virtualized a few times now, and it’s never been simple. I’m sure if you master it then it’s great, but the initial setup is a hard sell.