Create a simple Virtualized List
While many of us have some or the other way have come across the UI performance issues in react which on digging take us to same old simplifying heavy DOM. It can be rendering large list items in list, lazy loading, image optimization, etc.
With time as the application scales up the simplifying DOM rendering becomes a complex process. Here we are looking into list rendering but in virtual manner. typically we developers use map function to render the list in rows. with this approach rendering thousands of rows browser will create same no. of DOM elements. The DOM in background is using CPU/GPU of users system. This is not it, the CPU consumption increase with user events such as scroll or performing CRUD operations which results reduced framerate, lag on UI, and screen freeze.
## What is the solution??
So how can we display thousands of rows in an efficient way?
Well there are several Libraries such as (react-virtualized) you can find but i want to explain what happens in the bacground of these libraries.
There is one rule to solve the problem i.e. Render only what is visible!!!
for example the ui can show only 20 rows at a time then we can write a logic where on user scroll event we cut those 20 rows (slice the array) and render only 20 rows. these rows change on the basis of user scroll.
we are changing the portion of "Window" or sliding it. this brings us to Sliding Window technique. Sliding Window is very important
approach that involves iterating over a collection of items with a fixed-size window, where the window slides over the collection.
*What we need in Sliding Window for virtual List *
- starting pointer
- end Pointer
- length of the window
Lets start with the Example -
We have 100000 rows to display, creating an array -
const LIST = Array.from({ length: 100000 }, (_, index) => index + 1);
Create a VirtualizedList Component, which will accept the LIST, Div Height, Div Width and item Height. your app.js will look like-
import VirtualizedList from "./Components/VirtualizedList";
import "./styles.css";
const LIST = Array.from({ length: 100000 }, (_, index) => index + 1);
export default function App() {
return (
<div className="App">
<VirtualizedList List={LIST} height={400} width={300} itemHeight={35} />
</div>
);
}
The first thing would be get start and end pointer for sliding for our window on list array. We will also calculate the window based on height and itemHeight passed props.
import React, { useState } from "react";
const VirtualizedList = ({ List, height, width, itemHeight }) => {
const [indices, setIndices] = useState([0, Math.floor(height / itemHeight)]);
const visibleList = List.slice(indices[0], indices[1] + 1);
return (
<>
<div
style={{ height, width, backgroundColor: "orchid", overflow: "auto" }}
>
{visibleList.map((item, index) => {
return (
<div
key={`item is ${item}`}
style={{
height: itemHeight,
backgroundColor: "greenyellow",
border: "2px solid grey"
}}
>
item is {item}
</div>
);
})}
</div>
</>
);
};
export default VirtualizedList;
At first the
start index = 0, end index = Math.floor(height / itemHeight);
for initially the visible list is sliced on start and end index.
providing some color abd border to understand the items
const visibleList = List.slice(indices[0], indices[1] + 1);
Now we add scroll event but we need to wrap the list div as shown below and add scroll event to parent div. The need to wrap the div is because we need to provide height to scroll with.
height: List.length * itemHeight
import React, { useState } from "react";
const VirtualizedList = ({ List, height, width, itemHeight }) => {
const [indices, setIndices] = useState([0, Math.floor(height / itemHeight)]);
const visibleList = List.slice(indices[0], indices[1] + 1);
const handleScroll = (e) => {
const { scrollTop } = e.target;
const newStartIndex = Math.floor(scrollTop / itemHeight);
const newEndIndex = Math.floor(
newStartIndex + Math.floor(height / itemHeight)
);
setIndices([newStartIndex, newEndIndex]);
};
return (
<>
<div
onScroll={(e) => handleScroll(e)}
style={{ height, width, backgroundColor: "orchid", overflow: "auto" }}
>
<div style={{ height: List.length * itemHeight }}>
{visibleList.map((item, index) => {
return (
<div
key={`item is ${item}`}
style={{
height: itemHeight,
backgroundColor: "greenyellow",
border: "2px solid grey",
}}
>
item is {item}
</div>
);
})}
</div>
</div>
</>
);
};
export default VirtualizedList;
But this won't run as expected because the list changes but the position of item is not visible on ui, to overcome that we will use absolute position with top sum of indices[0] and current index multiple by itemHeight.
the final code -
import React, { useState } from "react";
const VirtualizedList = ({ List, height, width, itemHeight }) => {
const [indices, setIndices] = useState([0, Math.floor(height / itemHeight)]);
const visibleList = List.slice(indices[0], indices[1] + 1);
const handleScroll = (e) => {
const { scrollTop } = e.target;
console.log(scrollTop);
const newStartIndex = Math.floor(scrollTop / itemHeight);
const newEndIndex = Math.floor(
newStartIndex + Math.floor(height / itemHeight)
);
setIndices([newStartIndex, newEndIndex]);
};
return (
<>
<div
onScroll={(e) => handleScroll(e)}
style={{ height, width, backgroundColor: "orchid", overflow: "auto" }}
>
<div style={{ height: List.length * itemHeight, position: "relative" }}>
{visibleList.map((item, index) => {
return (
<div
key={`item is ${item}`}
style={{
height: itemHeight,
backgroundColor: "greenyellow",
border: "2px solid grey",
textAlign: "center",
position: "absolute",
width: "100%",
top: (indices[0] + index) * itemHeight,
}}
>
item is {item}
</div>
);
})}
</div>
</div>
</>
);
};
export default VirtualizedList;
Top comments (0)