Supercharge the Way You Render Large Lists in React

Rendering large lists in React can be a challenging task for developers. As the size of the list grows, the DOM (Document Object Model) tree also grows, leading to performance issues like slow rendering, janky scrolling, and high memory usage. In this article, we will discuss some of the common problems that developers face while rendering large lists, and various solutions that can be used to overcome them.

Why is rendering a massive list in a web browser a challenging task? Well, there are a few factors to consider while rendering a massive list of items. Firstly it’s the performance, as the number of elements to render on the screen increases, the browser’s rendering engine starts hitting performance issues. This would lead to slow rendering, resulting in a sluggish user interface and poor user experience.

Manipulating a massive list of elements will be computationally expensive. Scrolling through a large list can be janky for your end users, and in the worst cases, they could end up with a completely unresponsive page. The negative impact on low-end devices like mobile phones would be even greater due to limited processing power and memory.

With all these issues considered, we as software developers need to employ optimization techniques and choose appropriate strategies.

This article revolves around rendering massive lists in React. Firstly we’ll define what we mean by a massive list. Then we’ll look into some possible solutions you can incorporate to solve the issues pointed out in the problem statement. Finally, we’ll jump into a few key things to keep in mind while rendering a massive list in browsers.

What do we mean by a massive list?

Image

Figure 1: Code snippet to render massive list

The definition of a massive list is dynamic. It depends entirely on your end user’s device. If they are all on high-end computers or mobile devices, the number of elements you can render safely on your page before seeing any performance throttling would be significantly high.

On the other hand, if your end user’s device is more modest with limited memory and processing capabilities, this threshold would be smaller than the one above. With the advancement in modern browsers, you can easily load a vast amount of DOM elements without having to worry much, but even so, if you start rendering a list of orders with 1,000 or more rows, you can consider it to be massive.

If you end up in a place where you have to render tens of thousands of complex list items, you would inevitably have to use one of the following solutions, no matter how capable your end user’s hardware is. Firstly we’ll start with a general solution that everyone can incorporate into their code:

Image

Figure 2: Render tree illustration

In general, keeping a lean render tree boosts overall performance, because the more DOM elements there are, the more space needs to be allocated by your browser. Additionally, more time will be taken by the browser in the layout stage. This becomes extremely critical while rendering a massive list. Reducing even a single div can bring out an observable difference in terms of performance. 

The best practice would be to use the minimum number of DOM elements to create your list item without affecting the design.

First, we’ll create a simple list of around 10,000 elements. Here’s the code sandbox for the same.

Let’s look into the Lighthouse performance of the page:

Image

Figure 3: Lighthouse score of massive list

First contentful paint0.9 seconds
Largest contentful paint3.0 seconds
Total blocking time2.1 seconds
Total performance score43

Now, let’s add one more div to the list item and get all the performance numbers.

Here’s the Lighthouse performance: 

Image

Figure 4: Lighthouse score with one extra div

First contentful paint1 second
Largest contentful paint3.3 seconds
Total blocking time3.3 seconds
Total performance score40

Now let’s jump into actual solutions, the first of which is using the infinite scroll technique. In simpler words, this technique means to only render the list items required to fill in the entire page length, and then add more items as the user scrolls down.

We can implement this using the react-infinite-scroller library.

Here’s the implementation of the same 10,000-item list using react-infinite-scroller. 

Let’s see how it affects our performance:

Image

Figure 5: Lighthouse score with infinite scroll 

First contentful paint1.0 seconds
Largest contentful paint2.5 seconds
Total blocking time160ms
Total performance score78

Our total performance scroll jumped from a base of 43 to 78, which is a massive gain.

This is because of the fact that we essentially chopped our list into tiny portions. Instead of rendering all 10,000 items at once, we only rendered the first few items needed to render the portion of the page in the user’s view. When the user scrolls down, the react-infinite-scroller library adds more items to the DOM.

Another solution to this problem is to use the windowing technique. 

Windowing (or virtualization) is a technique where you only render the portion of the list that is visible to the user at any time. Unlike infinite scroll, in windowing the DOM always has a constant number of elements. In other words, we only render the required elements needed to fill the user’s field of vision and remove the elements from the top and bottom of the list that are not in the view yet.

Image

Figure 6: Windowing technique illustration

Windowing shines when each item in your list has a fixed height. That way it becomes easy to calculate which items need to be added or removed from the DOM based on the scroll. Furthermore, the memory usage of your page stays constant, as the number of DOM elements stays constant, no matter where the user has scrolled.

Here’s the implementation of the same 10,000-item list using react-window.

Let’s see how it affects our performance:

Image

Figure 7: Lighthouse score of massive list with windowing

First contentful paint1.0 seconds
Largest contentful paint2.5 seconds
Total blocking time190ms
Total performance score76

With both these techniques, you lose the capability to perform a browser search. In other words, if the user performs a cmd + f search for a text that hasn’t been rendered yet, they’ll see 0 of 0 matches. To prevent this, you should add custom search or filtering options to your lists.

There will also always be minor delays in adding/removing elements from the DOM, as compared to native scrolling, but that’s still better than your application ultimately hanging due to massive amounts of DOM elements.

If you want to keep your solution lightweight and don’t want to use any libraries, you can also implement your own lazy loading of DOM elements by using an Intersection Observer API

First, add enough components to fill up your viewport. Get the viewport height using getBoundingClientRect and divide that by your approximate minimum item height. Add 1 to this count for good measure. Now add a dummy component below it (this component doesn’t need to show anything; it just needs to exist at the bottom of your list). Attach the intersection observable to this component, and whenever this component is in view, add more items to your list to be rendered in the DOM. This way you’ll end up developing a simple infinite-scroll solution.

You should now be able to tackle large lists in a React application. In simple terms, you should only render a small portion of the list at a time and make sure there are enough filters present in the view for the users to slice and dice through the list.

首页 - Wiki
Copyright © 2011-2024 iteam. Current version is 2.139.0. UTC+08:00, 2024-12-28 05:31
浙ICP备14020137号-1 $访客地图$