Editor’s note: This article was last updated on 9 June 2023 to provide more context about the react-window components, such as AutoSizer
, CellMeasurer
, and 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.
Jump ahead:
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.
See the CodeSandbox example here.
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.
See the CodeSandbox example here.
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:
No windowing | Windowing | |
---|---|---|
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.
The quote below is from the react-window
GitHub by Brian Vaughn, aka bvaughn
, the author of both react-window and react-virtualized (and also a past team member of the React core team):
“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:
ArrowKeyStepper
AutoSizer
CellMeasurer
ColumnSizer
InfiniteLoader
MultiGrid
ScrollSync
WindowScroller
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.
AutoSizer
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 height
and 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.
See the CodeSandbox example here.
CellMeasurer
The following example implements logic through a custom React hook to simulate what react-virtualized’s CellMeasurer
offers.
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:
Note: there are some caveats to the approach in the demo above (as there are caveats to using the actual CellMeasurer
in react-virtualized).
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.
InfiniteLoader
While with react-virtualized you get a built-in InifiniteLoader
component, with react-window you would have to use an extra package: react-window-infinite-loader
.
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.
See the CodeSandbox example here.
ArrowKeyStepper
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 (ArrowUp
, 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.
MultiGrid
+ ScrollSync
The following example combines react-window’s FixedSizeList
and FixedSizeGrid
to achieve what react-virtualized’s MultiGrid
and ScrollSync
offer.
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.
See the CodeSandbox example here.
Quoting from the react-window GitHub again:
“If 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
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
2 Replies to "react-virtualized vs. react-window"
“Without windowing, React has to write your entire list to the DOM before one list item is visible.” This statement although correct is not the entire reason why one should use a virtualized list, imho scrolling is the main reason here. Scrolling a dom list with thousands of nodes feels like you’ll die of old age before you get to the other side.
As suggested by the author, we created window-table, a library for creating tables using react-window. https://window-table.netlify.com/