Infinite scrolling is where we load a part of the result up front, and on reaching the end of the loaded list we load the next set of results and so on.
So what is the problem?
Let us say that there is a list with 10,000 elements, say each element is displayed inside an <li> tag. So when we reach the end of the list then there will be 10,000 <li> nodes attached to the parent.
In case of complex <li> with more number of children inside it , this will cause a huge hit in the website's performance and also affects scrolling performance of the webpage.
How to overcome this overloaded DOM size?
- It can be overcome by Unloading or discarding the top
<li>which are not part of the user viewport. - (i.e) As and when the user scrolls down we start adding the new rows and should delete the existing rows which are above the fold.
- We will be achieving this with fixed children size, say 15 rows max and we will update the same for new set of inputs thus maintaining a lower DOM size.
Things to consider:
*Adding new node to the bottom of the list should be done when the last element of the list enters the viewport.
*After adding the new elements to the DOM the existing elements should be deleted such that the deleted elements lies above the fold.(i.e) out of user's viewport.
*When the user scrolls up and reaches the top element then we should load the deleted top elements and should delete the bottom ones, which are below the viewport.
What are we gonna Implement?
A Component that can render a list of n number of items with fixed window size of 15 elements.(i.e) At any time only 15 DOM nodes will be present even on infinite scrolling through n elements.
Output:
Approach:
We are going to get some help from CSS in achieving this.(We will make use of CSS's Absolute positioning).
Our Parent div which wraps all our element is going be of
relativein position.All our children will be positioned
absolutely with respect to the parent div.We calculate the
topfor each of the child and apply it in style parameter of the<li>.At any given time we will maintain 15
<li>elements in the DOM maximum.
Note: For Simplicity, we are going to consider only fixed size <li> cases.
Implementation:
Initialization:
import React from "react";
const THRESHOLD = 15;
class SlidingWindowScroll extends React.Component {
constructor(props) {
super(props);
this.state = {
start: 0,
end: THRESHOLD
};
this.$topElement = React.createRef();
this.$bottomElement = React.createRef();
}
}
- We have created our
SlidingWindowScrollcomponent which has a state withstartvalue andendvalue in it. -
startis the starting index of the list array which has to be loaded lazily on scroll. -
endis the last index of the list array. -
THRESHOLDgives the maximum number of<li>elements that should be present in the DOM. -
We create two refs:
-
this.$topElement, will point the first element(0th index) in the list item. -
this.$bottomElement, will point the last element(14th index) in the list item.
-
Whenever the new elements are added or deleted the refs has to be updated accordingly to point to the top and bottom of the presently rendered list.
Render:
getReference = (index, isLastIndex) => {
if (index === 0)
return this.$topElement; // Attach this ref for first element
if (isLastIndex)
return this.$bottomElement; // Attach this ref for last element
return null;
}
render() {
const {list, height} = this.props;
const {start, end} = this.state;
const updatedList = list.slice(start, end);
const lastIndex = updatedList.length - 1;
return (
<ul style={{position: 'relative'}}>
{updatedList.map((item, index) => {
const top = (height * (index + start)) + 'px';
const refVal = this.getReference(index, index === lastIndex);
const id = index === 0 ? 'top' : (index === lastIndex ? 'bottom' : '');
return (<li className="li-card" key={item.key} style={{top}} ref={refVal} id={id}>{item.value}</li>);
})}
</ul>
);
}
- We get the
listandheightfrompropsandstartandendof the list fromstate. -
updatedListgives the new set of elements to be rendered. -
<ul>is maderelative. - For each item in the list, we calculate it's
topposition from its relative parent. - It is calculated by the position of the current item in the
list(index + start) multiplied byheightof each element. -
refValgives therefthat has to be attached. It will have reference tothis.$topElementin case of index 0 and reference tothis.$bottomElementin case of last index. - We attach
idwith valuetopfor first element andbottomas id for last element.
Setting up the Observer for refs:
componentDidMount() {
this.intiateScrollObserver();
}
componentDidUpdate(prevProps, prevState) {
if ((prevState.end !== this.state.end) || (prevState.start !== this.state.start)) {
this.intiateScrollObserver();
}
}
- On
Mountingand on whenever the value forstartorendchanges therefwhich points to the top and bottom of the rendered<li>is changed. - Since the
refstarts pointing to different element we will have to listen to those two refs to know when they come into viewport.
We use IntersectionObserver to identify if the root or bottom element is in the viewport.
intiateScrollObserver = () => {
const options = {
root: null, // To listen to window scroll
rootMargin: '0px', // if there is any margin associated with it
threshold: 0.01 // if 1% of the element is in view
};
this.observer = new IntersectionObserver(this.callback, options);
if (this.$topElement.current) {
this.observer.observe(this.$topElement.current);
}
if (this.$bottomElement.current) {
this.observer.observe(this.$bottomElement.current);
}
}
- We create our
IntersectionObserverwith acallbackthat should get fired when the elements enters and leaves the viewport andoptions - In
optionswe specify that we are listening to the scroll event in window and the element should be marked as visible even when 1% of the element comes into view(by means ofthresholdkey). - Then, we observe both the refs (
this.$topElementandthis.$bottomElement) to know when it enters/ leaves viewport.
Handling viewport entry of <li>
callback = (entries, observer) => {
entries.forEach((entry, index) => {
const listLength = this.props.list.length;
const {start, end} = this.state;
// Scroll Down
// We make increments and decrements in 10s
if (entry.isIntersecting && entry.target.id === "bottom") {
const maxStartIndex = listLength - 1 - THRESHOLD; // Maximum index value `start` can take
const maxEndIndex = listLength - 1; // Maximum index value `end` can take
const newEnd = (end + 10) <= maxEndIndex ? end + 10 : maxEndIndex;
const newStart = (end - 5) <= maxStartIndex ? end - 5 : maxStartIndex;
this.updateState(newStart, newEnd);
}
// Scroll up
if (entry.isIntersecting && entry.target.id === "top") {
const newEnd = end === THRESHOLD ? THRESHOLD : (end - 10 > THRESHOLD ? end - 10 : THRESHOLD);
let newStart = start === 0 ? 0 : (start - 10 > 0 ? start - 10 : 0);
this.updateState(newStart, newEnd);
}
});
}
- Whenever
this.$topElementorthis.$bottomElementcomes into viewport or leaves the viewportcallbackwill be called. -
entriesis an array with all observers in the order of creation. -
entriesin our case will havethis.$topElementandthis.$bottomElement. -
isIntersectingproperty gives if the element is in viewport andidhelps us in deciding if it is the bottom element that came into view or the top one. - We make calculation to maintain 15 elements between
startandendof thestate. - We add and remove items in number of 10 and we make sure atleast 15 elements are present.
- Finally, we update
statewith new values forstartandend.
Updating State:
resetObservation = () => {
this.observer.unobserve(this.$bottomElement.current);
this.observer.unobserve(this.$topElement.current);
this.$bottomElement = React.createRef();
this.$topElement = React.createRef();
}
updateState = (newStart, newEnd) => {
const {start, end} = this.state;
if (start !== newStart || end !== newEnd) {
this.resetObservation();
this.setState({
start: newStart,
end: newEnd
});
}
}
- We set the
statewith new values and also reset all the observer. - While resetting, all the observers should be made
unobserveto not to observe it's change in the future. And we create a newrefforthis.$bottomElementandthis.$topElement.
Now on scroll we have only 15 elements at a time but giving the user the sense of having it all in the DOM.
Have a unique key to avoid re-rendering of same <li>s.
Output:
Follow me for interesting contents.
Repo Link
My Website, blogs and Twitter
That's All Folks!



Top comments (3)
This is awesome! I appreciate the tutorial!
Thank you for this amazing tutorial... cleared most of my doubts regarding DOM recycling
Glad that it helped :)