When there is a large number of records to be fetched for a particular query and because fetching such huge data and rendering on the screen will downgrade the application performance.
To solve this issue we need to fetch and show the user some records initially and as the user reaches the end of the list we can request for more data. In this way, we can achieve building an infinite scrolling list by fetching and displaying only what needs to be fetched.
This is what we are going to build.
After scrolling down, App will fetch more data from the Server and display it here.
Prerequisites and Target Audience
You need to have Node.js installed in your system and mobile phone running either Android or iOS. This article is intended for readers with a basic understanding about frontend and backend coding and development environment along with basic command line knowledge.
Architecture and Process Flow
So, basically we need two things to setup pagination:
- Page No: The page number at which the user is currently.
- Page Size: Number of records that needs to be fetched at a time.
For the first load following structure is followed:
Now if we don’t have any data to be shown we can display no records found message.
But if we have data and the user scrolls and reaches the last visible record we need to fetch more records as follows:
So, the data is displayed and further paging continues as the user scrolls and the last element is reached unless there are more no records to be fetched.
Setting up backend
First create a new Node project. Open up your terminal and create a new project folder.
$ cd infinite_scroll_backend
Create new Node project using the following command:
Install the dependencies, we need express module for now.
Now create a new file named app.js and copy/paste the following code:
const app = express()
const router = express.Router()
router.get('/fetch-paginated-data', (req, res) => {
var pageNo = parseInt(req.query.pageNo)
var pageSize = parseInt(req.query.pageSize)
//checking if page number is invalid
if (pageNo <= 0) {
var response = {
success: false,
message: 'Invalid Page Number'
};
return res.status(200).json(response);
} else {
//fetch data from database based on given page no and page size
var index = (parseInt(pageNo - 1) * parseInt(pageSize)) + 1;
var list = [];
for (var i = 0; i < pageSize - 1; i++) {
list.push({
index: index,
data: 'Data ' + index
});
index++;
}
var response = {
success: true,
list: list
};
return res.status(200).json(response);
}
});
app.use('/', router)
app.listen(3000);
You can run the code using the following command:
Now we have a back end simulation ready which will keep on giving us data for any page number and any page size request.
Building React Native App
We will be building a react native application by using expo more information can be found at https://expo.io/
You need to install the expo command line tool. Here is the command: (You may need to provide sudo access)
Once expo is successfully installed then you can create a new React native project using the following command:
It will ask question such as which template to choose and which workflow to use. Choose blank template and managed workflow option.
Refer this screenshot for the same.
Switch to the project folder and run the code using the following command.
Now a browser window should open, something like this.
Now in order to test our app in mobile devices. We need to download the expo app on our phone. Refer the link below for Android and iOS app.
CLICK HERE TO DOWNLOAD EXPO ON ANDROID
CLICK HERE TO DOWNLOAD EXPO ON IOS
Now scan the QR code using the Expo app.
If everything is fine, you should see the following screen on your phone.
Let’s code our app now, install the following dependencies in your react native project.
Now open up the App.js and copy/paste the following code.
import { StyleSheet, ActivityIndicator, View } from 'react-native';
import { Container, Content, Text, List } from 'native-base';
import axios from 'axios';
export default class App extends React.Component {
constructor(props) {
super(props);
//initialize state values
this.state = {
pageNo: 1,
pageSize: 20,
showLoadingMore:false,
data: [],
loadMoreData: true,//to denote whether bottom of list is reached
shouldHit: true, //whether more data needs to be fetched
dataReceived: false, //whether initial data is fetched
}
}
componentWillMount = () => {
this.fetchData();
}
//function to fetch more data as per current page number
fetchData = () => {
if(this.state.pageNo!=1){
//when we try to fetch more data show loader at the bottom
this.setState({
showLoadingMore:true
})
}
var systemIPAddress='localhost';
var url = 'http://'+systemIPAddress+':3000/fetch-paginated-data?pageNo='+this.state.pageNo+'&pageSize='+this.state.pageSize;
axios
.get(url)
.then(response => {
if (response.data.success) {
//add data to list and change the state to render new content
let receivedDataList = response.data.list;
let currentDataList = this.state.data;
//append to existing list
let newDataList = currentDataList.concat(receivedDataList);
//render new list
//once new list is set we are ready to load more data if bottom is reached
let loadMoreData =true;
this.setState({
pageNo:this.state.pageNo+1,
data:newDataList,
dataReceived:true,
loadMoreData: loadMoreData,
showLoadingMore:false
})
} else {
//no more data to be loaded
this.setState({
shouldHit:false,
showLoadingMore:false
})
}
})
.catch(error => {
console.log(error)
});
}
render() {
const isCloseToBottom = ({ layoutMeasurement, contentOffset, contentSize }) => {
const paddingToBottom = 40;
let result = layoutMeasurement.height + contentOffset.y >= contentSize.height - paddingToBottom;
//true if the end is reached other wise false
return result;
};
//initially display loader at the center
let listSection =
<View style={ styles.container} >
<ActivityIndicator size="large" color="#0000ff" />
</View>
if (this.state.dataReceived) {
if (this.state.data.length > 0) {
listSection = this
.state
.data
.map((record) => {
return (
<View key={record.index} style={[styles.container,{ margin: 10, height:40, borderWidth:1, borderColor:'black'}]}>
<Text>{record.index}. {record.data}</Text>
</View>
);
})
} else {
listSection = null;
}
}
if (this.state.dataReceived && this.state.data.length == 0) {
return (
<View style={styles.container}>
<Text>No records to display</Text>
</View>
)
} else {
return (
<Container style={{marginTop:40}}>
<Content
onScroll={({ nativeEvent }) => {
if (isCloseToBottom(nativeEvent)) {
//prevent multiple hits for same page number
if(this.state.loadMoreData){
//bottom reached start loading data
this.setState({
loadMoreData:false
})
this.fetchData();
}
}
}}>
<List>
{listSection}
</List>
{this.state.showLoadingMore ? <ActivityIndicator size="large" color="#0000ff" />: null}
</Content>
</Container>
)
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Run the code again and scan the QR code using the expo app on your mobile phone and try out the app.
Demo
Check out the demo video.
Android:
iOS:
Conclusion
We made a simple Node server that returns us data in a paginated format. We developed a small React app that reads that data and loads it on App as the user scrolls.