Editor’s note: This article was last updated on 9 June 2023 to provide more context about the react-window components, such as
InfiniteLoader. Check out “How to virtualize large lists using react-window” for more information.
If there is one thing known for being expensive when it comes to developing web pages, it’s manipulating the DOM. React itself aims to decrease the number of times we directly interact with the DOM.
The libraries we’ll discuss in this article help manipulate the DOM in a more effective way when rendering an extensive list of items. They work with the premise that items in a list that are not currently being shown to the user don’t need to exist in the DOM just yet.
react-window and react-virtualized are two good examples of libraries that calculate which items need to be added to the DOM at each moment, which is a process called list virtualization, also known as windowing.
In this article, we will compare react-window and react-virtualize, as they are similar but offer different levels of support and overhead to your project.
List virtualization, or windowing, is a technique for improving the performance of rendering long lists by only writing the visible portion of the list to the DOM.
This works by having a small window element moving over a bigger container, which will host the items, but will only render the items that are currently visible to the user through that window. A few other items in the list, which are outside the limits of the window but are close to the upper and lower boundaries, might be rendered so that when they enter into the view, they will already be loaded into the page. This gives the user a more natural scrolling experience.
Without windowing, React has to write your entire list to the DOM before one list item is visible. So if I had 10,000 list items, I’d have to wait for React to write at least 10,000
<div />s to the DOM before the first item in that list is visible. Ouch.
As mentioned at the beginning of this article, React internally uses a virtual DOM to hold your UI state because the real DOM is slow and expensive. By windowing, you can speed up your initial render by avoiding interacting with the real DOM as much as possible.
Though it can improve performance, windowing is not a silver bullet. Windowing improves performance because it delays writing your entire list to the DOM, but the reality is that those items have to be written to the DOM eventually if you want the user to see them. If you don’t pay for the rendering time upfront, then you’ll have to pay for it later.
Sometimes, windowing can actually decrease perceived performance because the user has to wait for each individual list item to load on scroll instead of waiting for one eager load of the entire list on mount.
In the demo above, notice how the list in the windowed version appears faster, but the non-windowed version feels faster when you’re scrolling through it. The windowed version appears faster because it delays rendering the whole list, but it feels slower and looks janky when scrolling fast because it’s loading and unloading list items to the DOM.
Whether or not to window greatly depends on your situation and what’s important to you:
|Initial load time
|⚠️ Depends on the list size
|✅ (near) Instant
|List item load time
|✅ (near) Instant
|⚠️ Depends on complexity of the item
|DOM manipulation occurs
|⚠️ On initial render
|⚠️ On scroll
In general, I would not recommend windowing if you don’t have to. I’ve made the mistake of windowing when it was unnecessary, and the end result was a slower list that took longer to make and was significantly more complex.
Both react-window and react-virtualized are great libraries that make windowing as easy as can be, but they also introduce a set of constraints on your UI. Before you use list virtualization, try making your list normally and see if your environment can handle it. If you’re having performance issues, then go for windowing.
react-window is a complete rewrite of
react-virtualized. I didn’t try to solve as many problems or support as many use cases. Instead, I focused on making the package smaller and faster. I also put a lot of thought into making the API (and documentation) as beginner-friendly as possible.”
As stated, the purpose of react-window is not to solve and support as many cases as react-virtualized. This might make you think that react-window won’t solve your problem, but that’s not necessarily the case. react-window is just a lighter core with a simpler philosophy.
react-window can still support many of the same use cases as react-virtualized, but it’s still your responsibility as a developer to use react-window as a building block instead of react-virtualized for every use case.
react-window is just a library that virtualizes lists and grids. That’s why it’s more than 15 times smaller. While adding react-window to your project has an overhead of <2KB, react-virtualized will add ~33.5 KB to the total build size.
Out of the box, react-window only has four components:
This is vastly different from the 13 components react-virtualized has, which include virtualized collection types:
And helpers/decorators for the above collection types:
As a general rule of thumb, you should be able to use react-window instead of react-virtualized for tables, lists, and grids. However, you can’t use react-window for anything else, including masonry layouts and other 2D layouts that don’t fit a grid.
The following are some demos for using react-window components to achieve the same results you would get with react-virtualized components.
This example shows how to use react-window’s
FixedSizeList component to implement a dynamic container size that will fill the remaining width and height available.
It works by using a similar approach used react-virtualized’s
AutoSizer. By having a resize listener registered to the element that holds the
FixedSizeList, we’re able to update the
width props passed to it whenever the size of the container changes.
In this case, by using react-window, we get the freedom to choose how we want to “listen” to the resize event and how we manipulate the values for the width and height.
The following example implements logic through a custom React hook to simulate what react-virtualized’s
In order to dynamically measure the size of a cell (the item in the list) based on the contents, it uses a hidden element written to the DOM. Once it has the size (width or height) of that cell, it will send that over to the
VariableSizeList component through the
itemSize prop and finally render it:
This cell measurer has to render the contents of the item twice: once to size it, and then once inside the list. This approach also requires the node to be rendered synchronously with react-dom, so complex list items may seem to appear slower when scrolling.
While with react-virtualized you get a built-in
InifiniteLoader component, with react-window you would have to use an extra package:
As mentioned, react-window’s purpose is to offer only the core components that you can use to achieve more complex scenarios. For other use cases, you can either implement them yourself or use another package of your choice.
The following example shows how that external package works together with react-window to implement an infinite loader within a list. The structure you’ll end up with is similar to how
InfiniteLoader works, where you pass the list component as a child to the infinite loader component. Even the props used have similar names and purposes.
The following example implements logic on top of the
FixedSizeList component from react-window to achieve what ArrowKeyStepper from react-virtualized offers.
Both implementations work similarly because they are based on arrow events (
ArrowDown, etc). Of course, the details in which they are implemented vary and one offers more support to edge cases than the other, but for more simple scenarios you might be good with just using react-window and implementing the handling of the keys on your own.
The following example combines react-window’s
FixedSizeGrid to achieve what react-virtualized’s
The goal here is to have a two-dimensional scrolling experience within a grid that is synced with two fixed-size lists, which represent the column and the row holding the labels. This example uses state variables to hold the scroll offsets in each axis, which are then shared between the three involved components.
This a good example of how core components from react-window can be easily combined to achieve complex scenarios that react-virtualized would solve with one component.
Quoting from the react-window GitHub again:
react-window provides the functionality your project needs, I would strongly recommend using it instead of
react-virtualized. However, if you need features that only
react-virtualized provides, you have two options:
react-virtualized. (It’s still widely used by a lot of successful projects!)
react-window primitives and adds the functionality you need. You may even want to release this component to npm (as its own, standalone package)! 🙂”
So there’s that! react-virtualized is a great project, but it may do more than you need. However, I would recommend using react-virtualized over react-window if:
react-window offers newer and faster virtualized list primitives. Use react-window as your virtualized list building block to satisfy your specific use case without bringing in a lot of unnecessary code.
react-virtualized is a heavier all-in-one tool that solves — and provides docs/examples for — many use cases, including virtualizing collections that are not grids (e.g., masonry layouts). react-virtualized is still a great library but probably does more than you need it to.
Install LogRocket via npm or script tag.
LogRocket.init() must be called client-side, not
CRDTs, or conflict-free replicated data types, is a concept that underlies applications facing the issue of data replication across a […]
We explore the fusion of TensorFlow and Rust, delving into how we can integrate these two technologies to build and train a neural network.
SignalDB enables automatic data synchronization between your components and a local in-memory or persistent database.