Infinite scroll has become a major feature in apps we use in our day to day life like Twitter Instagram and just generally content feed apps that just want your undivided attention daily, on a functionality point of view infinite scroll outperforms pagination approaches to loading data due to it being seamless to the user and only loads more data when the user reaches the end of the scroll.
Infinite scroll
is a feature where data is loaded onto the user’s page when the user reaches the end or almost the end of the scroll page, this is done by calling a paginated API,
A paginated API for reference is an API that returns a list of data whenever we call the API and can return different sets of data based on the page count that we passed into it an example of a paginated API would be the API we use in this example
`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`
page
is the variable we pass into the API it is going to be a number that we track and increment after loading every page.
Infinite scroll
although an excellent approach to loading data isn't the most optimal for all projects, some projects do function better with pagination, but infinite scrolling works best when loading related data that is loaded in a preferably chronological order based on time or relevance, pagination though is useful when users need to load data that far back, let's say you have some bank transaction records and you know that the records are a month away you can skip to the farthest page and circle back if you overshoot the page, but in reality, infinite scroll and a good date filter can solve that problem
Building this application would require some basic knowledge of a couple of things we would be using in our application.
In react we have 2 options to implement infinite scroll in our app.
A fast way to implement infinite scrolling in react would be to use a third-party library one of my go-to libraries for this feature would be the react-infinite-scroll-component
.
react-infinite-scroll-component
is a simple library that exports an <InfiniteScroll/>
component that can be used in our application and its feature-rich with props and events you can call before and after loading more data into the app, also a cool thing would be a refresh function you can call whenever you want to load new data to the top of your table.
npm install --save react-infinite-scroll-component
or
yarn add react-infinite-scroll-component
In our App.jsx
import React from "react";
import InfiniteScroll from "react-infinite-scroll-component";
import axios from "axios";
let page = 1;
const fetchData = (setItems, items) => {
axios
.get(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`)
.then((res) => {
setItems([...items, ...res.data]);
page = page + 1;
});
};
const refresh = (setItems) => {};
export default function App() {
const [items, setItems] = React.useState([]);
React.useEffect(()=>{
fetchData(setItems,items)
},[])
return (
<InfiniteScroll
dataLength={items.length} //This is important field to render the next data
next={() => {
fetchData(setItems, items);
}}
hasMore={true}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: "center" }}>
<b>Yay! You have seen it all</b>
</p>
}
// below props only if you need pull down functionality
refreshFunction={refresh}
pullDownToRefresh
pullDownToRefreshThreshold={50}
pullDownToRefreshContent={
<h3 style={{ textAlign: "center" }}># 8595; Pull down to refresh</h3>
}
releaseToRefreshContent={
<h3 style={{ textAlign: "center" }}># 8593; Release to refresh</h3>
}
>
<div style={{ minHeight: "100vh" }}>
{items.map((user) => (
<img src={user.url} height="100px" width="200px" />
))}
</div>
</InfiniteScroll>
);
}
Let's break our code down into smaller bits.
let page = 1;
const fetchData = (setItems, items) => {
axios
.get(`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`)
.then((res) => {
setItems([...items, ...res.data]);
page = page + 1;
});
};
The fetch
function is able to call our API to get new data it's triggered by the <InfiniteScroll/>
component when we scroll to the end of the view, there is a count variable we use to monitor the page loaded and it is incremented after the data is loaded.
const [items, setItems] = React.useState([]);
React.useEffect(()=>{
fetchData(setItems,items)
},[])
React effect is used to load the first batch of data into the view, we pass the systems function and the items variable into the function (something new I should have been doing a while back to remove API calls from my component)
<InfiniteScroll
dataLength={items.length} //This is important field to render the next data
next={() => {
fetchData(setItems, items);
}}
hasMore={true}>
///////
/// code
///////
>
<div style={{ minHeight: "100vh" }}>
{items.map((user) => (
<img src={user.url} height="100px" width="200px" />
))}
</div>
</InfiniteScroll>
We call our component and pass data into it if you need documentation you can check it out here https://www.npmjs.com/package/react-infinite-scroll-component
.
Here is the output.
Implementing a scroll component can be a nice learning project and gives you more control than when you use a component and is pretty easy to set up but can take a bit of time to perform research on how to get it done, luckily I've done that for you.
import React, { Component } from "react";
class ScrollComponent extends Component {
constructor() {
super();
this.state = {
loading: false,
page: 0,
prevY: 0
};
}
async getItems() {
try {
await this.props.loadData();
} catch (error) {
console.log(error);
}
}
componentDidMount() {
this.getItems();
var options = {
root: null,
rootMargin: "0px",
threshold: 1.0
};
this.observer = new IntersectionObserver(
this.handleObserver.bind(this),
options
);
this.observer.observe(this.loadingRef);
}
async handleObserver(entities, observer) {
const y = entities[0].boundingClientRect.y;
if (this.state.prevY > y) {
this.setState({ loading: true });
console.log(this.state);
await this.getItems();
this.setState({ loading: false });
console.log(this.state);
}
this.setState({ prevY: y });
}
render() {
// Additional css
const loadingCSS = {
height: "100px",
margin: "30px"
};
// To change the loading icon behavior
const loadingTextCSS = { display: this.state.loading ? "block" : "none" };
return (
<div className="container">
<div style={{ minHeight: "800px" }}>
{/* {this.state.photos.map(user => (
<img src={user.url} height="100px" width="200px" />
))} */}
{this.props.children}
</div>
<div
className="house"
ref={(loadingRef) => (this.loadingRef = loadingRef)}
style={loadingCSS}
>
<span style={loadingTextCSS}>Loading...</span>
</div>
</div>
);
}
}
export default ScrollComponent;
And in our app.jsx
component we replace the <InfiniteScroll/>
and insert our new component.
import React from "react";
import axios from "axios";
import ScrollComponent from "./scroll";
let page = 1;
const fetchData = async (setItems, items) => {
const data = await axios.get(
`https://jsonplaceholder.typicode.com/photos?_page=${page}&_limit=10`
);
setItems([...items, ...data.data]);
page = page + 1;
};
const refresh = (setItems) => {};
export default function App() {
const [items, setItems] = React.useState([]);
React.useEffect(() => {
fetchData(setItems, items);
}, []);
return (
<ScrollComponent
loadData={() => {
fetchData(setItems, items);
}}
>
<div style={{ minHeight: "100vh" }}>
{items.map((user) => (
<img
key={Math.random()}
src={user.url}
height="100px"
width="200px"
/>
))}
</div>
</ScrollComponent>
);
}
Let's break our component down into smaller bits so we can understand it.
componentDidMount() {
this.getItems();
var options = {
root: null,
rootMargin: "0px",
threshold: 1.0
};
this.observer = new IntersectionObserver(
this.handleObserver.bind(this),
options
);
this.observer.observe(this.loadingRef);
}
Our componentDidMount
function is run as soon as our component is started and adds an IntersectionObserver
observer to the component that checks out the house
and measures the difference between it and the this.props.children
and calls the handleObserver
function when the observer is triggered.
async handleObserver(entities, observer) {
const y = entities[0].boundingClientRect.y;
if (this.state.prevY > y) {
this.setState({ loading: true });
console.log(this.state);
await this.getItems();
this.setState({ loading: false });
console.log(this.state);
}
this.setState({ prevY: y });
}
Our handleObserver
example function calls out the update function that's passed into the props, this is powerful because we can use the concept of dependency injection to pass in the update function from our component making this component agnostic to its use case
const [items, setItems] = React.useState([]);
React.useEffect(() => {
fetchData(setItems, items);
}, []);
We take advantage of react useEffect to set up how we manage data in our component, we need to pass setItems
and items
into the fetchdata component to pass control initoo the function,
render() {
// Additional css
const loadingCSS = {
height: "100px",
margin: "30px"
};
// To change the loading icon behavior
const loadingTextCSS = { display: this.state.loading ? "block" : "none" };
return (
<div className="container">
<div style={{ minHeight: "800px" }}>
{/* {this.state.photos.map(user => (
<img src={user.url} height="100px" width="200px" />
))} */}
{this.props.children}
</div>
<div
Class = ‘house’
ref={(loadingRef) => (this.loadingRef = loadingRef)}
style={loadingCSS}
>
<span style={loadingTextCSS}>Loading...</span>
</div>
</div>
);
}
Our render function renders our child component passed into the component, this lets us reuse our component for different types of use cases.
Replacing our component in the App.js
<ScrollComponent loadData={()=>{
fetchData(setItems, items);
}}>
<div style={{ minHeight: "100vh" }}>
{items.map((user) => (
<img src={user.url} height="100px" width="200px" />
))}
</div>
</ScrollComponent>
Our output(similar to our old implementation).
Infinite scrolling
is becoming an amazing way of showing feed data because it offers a nonstop flow of data that is addictive (talking from a users point of view) and only loads new data when it reaches the end of the page, this is done by monitoring the page count and incrementing the page seen page at the end of every load.
In this guide, we learned 2 different modes of implementing this feature in react,
Each approach gives the same result but comes with different pros and cons that make them perfect for different situations, I personally keep a copy of my own custom component on my PC and copy the custom component into my new project, it helps to keep it flexible for different project seeing as it is just a component and can be called whenever it's needed, also the concept of injecting the load function makes it easy to use and re-use across projects.
I hope this article was helpful to you, cheers and till next time!
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!