Infinite Scroll implementation in React Native and Node

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.

Infinite Scroll Node and React

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.

$ mkdir infinite_scroll_backend
$ cd infinite_scroll_backend

Create new Node project using the following command:

npm init --y

Install the dependencies, we need express module for now.

npm i --S express

Now create a new file named app.js and copy/paste the following code:

app.js
const express = require('express')
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:

node app.js

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)

npm install expo-cli --global

Once expo is successfully installed then you can create a new React native project using the following command:

expo init

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.

cd infinite_scroll_demo && npm start

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.

Android
Apple iOS

Let’s code our app now, install the following dependencies in your react native project.

npm i --S axios native-base @expo/vector-icons

Now open up the App.js and copy/paste the following code.

App.js
import React from 'react';
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.

Shahid
Shahid

Founder of Codeforgeek. Technologist. Published Author. Engineer. Content Creator. Teaching Everything I learn!

Articles: 126