DEV Community

Michael Lustig - halfjew22@gmail.com
Michael Lustig - halfjew22@gmail.com

Posted on • Edited on

I created the simplest implementation of an infinite paging FlatList using Hooks in ReactNative.

Would love any feedback and hope it helps!

Next, I'm going to be creating an infinite-paging, live-updating, load-new-posts-button having list with Firebase and hooks. Stay tuned!

Let me know your thoughts! This is my first post on Dev, sorry it's a little short! Just wanted to get my feet wet.

Gist: https://gist.github.com/technoplato/e394369a6f202a58bf010635e6eb32c7

import React, { useState, useEffect } from 'react'
import {
  SafeAreaView,
  View,
  FlatList,
  StyleSheet,
  Text,
  Dimensions
} from 'react-native'

const useInfiniteScroll = load => {
  const [isFetching, setIsFetching] = useState(true)
  const [data, setData] = useState([])

  useEffect(() => {
    let didCancel = false
    if (!isFetching) return

    const loadAsync = async () => {
      const lastIndex = data.length
      const lastItem = data.length ? data[lastIndex] : null

      const newData = await load({ lastIndex, lastItem })
      if (!didCancel) {
        setData(prevState => [...prevState, ...newData])
        setIsFetching(false)
      }
    }

    loadAsync()

    return () => {
      didCancel = true
    }
  }, [isFetching])

  return [data, isFetching, setIsFetching]
}

const INITIAL_LOAD = 30
const PAGE_SIZE = 20

export default () => {
  /**
   * Right now, I'm mandating that whatever this method is accepts as a
   * parameter an object containing the objects `lastIndex` and `lastObject`
   * respectively. I believe this should suffice for effective paging.
   *
   * @param lastIndex
   * @returns {Promise<R>}
   */
  const fetchMoreListItems = ({ lastIndex }) => {
    // Simulate fetch of next 20 items (30 if initial load)
    return new Promise(resolve => {
      setTimeout(() => {
        resolve([
          ...Array.from(
            Array(lastIndex === 0 ? INITIAL_LOAD : PAGE_SIZE).keys(),
            n => {
              n = n + lastIndex
              return {
                number: n.toString(),
                id: n.toString()
              }
            }
          )
        ])
      }, 2000)
    })
  }

  const [data, isFetching, setIsFetching] = useInfiniteScroll(
    fetchMoreListItems
  )

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.blueBox}>
        <Text style={styles.bigWhiteBoldText}>
          {`${data.length} Items Loaded`}
        </Text>
      </View>
      <FlatList
        onEndReachedThreshold={7}
        onEndReached={() => {
          if (!isFetching) {
            setIsFetching(true)
          }
        }}
        data={data}
        keyExtractor={item => item.id}
        renderItem={({ item }) => {
          return <Item item={item} />
        }}
      />
      {isFetching && (
        <View style={styles.blueBox}>
          <Text style={styles.bigWhiteBoldText}>(Fetching More)</Text>
        </View>
      )}
    </SafeAreaView>
  )
}

class Item extends React.PureComponent {
  render() {
    return (
      <View style={styles.item}>
        <Text style={styles.title}>{this.props.item.number}</Text>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 24,
    backgroundColor: 'yellow'
  },
  item: {
    backgroundColor: '#f9c2ff',
    alignItems: 'center',
    justifyContent: 'center',
    height: Dimensions.get('window').height * 0.45,
    marginVertical: 8,
    marginHorizontal: 16
  },
  title: {
    fontSize: 48
  },
  blueBox: {
    height: 50,
    backgroundColor: 'blue',
    justifyContent: 'center',
    alignItems: 'center'
  },
  bigWhiteBoldText: {
    color: 'white',
    fontSize: 32,
    fontWeight: 'bold'
  }
})
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
milansusnjar_ profile image
Milan Šušnjar

const lastIndex = data.length
const lastItem = data.length ? data[lastIndex] : null

If your data has 2 objects, lastIndex = 2
lastItem = data[2] but you only have 0 and 1.

Last index should be data.length - 1

Collapse
 
technoplato profile image
Michael Lustig - halfjew22@gmail.com

You’re absolutely right, except for the length == 0 case. So shouldn’t it be data.length ? data.length - 1 : 0?

Collapse
 
milansusnjar_ profile image
Milan Šušnjar

Like this, I guess.
const lastIndex = data.length - 1
const lastItem = data.length ? data[lastIndex] : null

Thread Thread
 
technoplato profile image
Michael Lustig - halfjew22@gmail.com

It depends on what we’d want to pass to the load method in the case of an empty result set. I think your way works great.

Bigger question though: can you imagine a way to make this more customizable to use any load function in a plug and play manner?