'FlatList infinite loop - React Native

I'm trying to make a Flatlist with infinite scrolling in both directions.

There is already a small outline of the implementation (https://snack.expo.dev/@slam_ua/flatlist-loop), but I can't seem to do a few things:

  1. Seamless list. Scrolling stops when data is updated.
  2. Infinite scroll up not working.
  3. Centering the initial coordinate (00) in the center (from the picture with an example it will be clear what I mean).

Below I have shown an example of the result I want to achieve:

enter image description here



Solution 1:[1]

We can tweak react-native-circular-wheel-picker to achieve this.

import React, { useEffect, useRef, useState } from "react"
import {
  NativeScrollEvent,
  NativeSyntheticEvent,
  FlatList,
  Text,
  View,
  StyleProp,
  TextStyle,
  ViewStyle,
  StyleSheet,
} from "react-native"

type dataType = {
  value: number | string
  label: number | string
}

interface WheelNumberPickerProps {
  data: dataType[]
  height: number
  textStyle?: StyleProp<TextStyle>
  selectedTextStyle?: StyleProp<TextStyle>
  unselectedTextStyle?: StyleProp<TextStyle>
  dividerWidth?: ViewStyle["borderBottomWidth"]
  dividerColor?: ViewStyle["borderBottomColor"]
  selectedValue?: number | string
  onValueChange?: (value: number | string) => void
}

function WheelNumberPicker({
  height = 25,
  textStyle,
  selectedTextStyle,
  unselectedTextStyle,
  dividerWidth = 1,
  dividerColor,
  selectedValue = 0,
  onValueChange,
  data = [],
}: WheelNumberPickerProps) {
  const [dataArray] = useState<dataType[]>([...data, ...data, ...data])
  const [value, setValue] = useState<number | string>(selectedValue)

  const flatListRef = useRef<FlatList>()
  const currentYOffset = useRef<number>(0)
  const numberOfValue = useRef<number>(data.length)
  const initialOffset = useRef<number>((data.length - 0.5) * height)

  useEffect(() => {
    if (!onValueChange) {
      return
    }
    onValueChange(value)
  }, [value, onValueChange])

  const onScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
    const offsetY = nativeEvent.contentOffset.y
    let index = Math.ceil((offsetY % initialOffset.current) / height)
    index = index < numberOfValue.current ? index : numberOfValue.current - 1
    const selectedValue = data[index].value
    if (value !== selectedValue) {
      setValue(selectedValue)
    }

    if (offsetY < currentYOffset.current) {
      if (offsetY <= initialOffset.current - height) {
        flatListRef.current?.scrollToOffset({
          offset: offsetY + height * numberOfValue.current,
          animated: false,
        })
        currentYOffset.current = offsetY + height * numberOfValue.current
        return
      }
    }

    if (offsetY > currentYOffset.current) {
      if (offsetY > initialOffset.current + height) {
        flatListRef.current?.scrollToOffset({
          offset: offsetY - height * numberOfValue.current,
          animated: false,
        })
        currentYOffset.current = offsetY - height * numberOfValue.current
        return
      }
    }

    currentYOffset.current = offsetY
  }

  return (
    <View style={{ alignItems: "center", justifyContent: "center" }}>
      <View
        style={{
          position: "absolute",
          borderTopWidth: dividerWidth,
          borderBottomWidth: dividerWidth,
          borderColor: dividerColor,
          height,
          width: height * 1.2,
        }}
      />
      <View style={{ width: height * 1.2, height: height * 5 }}>
        <FlatList
          data={dataArray}
          onScroll={onScroll}
          ref={flatListRef}
          showsVerticalScrollIndicator={false}
          snapToAlignment="center"
          snapToInterval={height}
          scrollEventThrottle={12}
          decelerationRate="fast"
          keyExtractor={(_, index) => index.toString()}
          renderItem={({ item }) => {
            return (
              <View
                style={{
                  width: "100%",
                  height,
                  alignItems: "center",
                  justifyContent: "center",
                }}>
                <Text style={[textStyle, selectedTextStyle]}>{item.label}</Text>
              </View>
            )
          }}
        />
      </View>
    </View>
  )
}

export default WheelNumberPicker

We use it as follows.

const [data] = useState(
    Array(24)
      .fill(0)
      .map((_, index) => {
        return {
          value: index,
          label: index < 10 ? "0" + index : index,
        }
      })
  )
  return (
    <View style={{ marginTop: 250 }}>
      <WheelNumberPicker height={30} data={data} />
    </View>
  )

The above yields to the following result.

enter image description here

Solution 2:[2]

You can achieve this by using simple picker libraries in React Native. The View/UI You want, you have to create components for them. you can use this library:

react-native-picker

npm i react-native-picker

import Picker from 'react-native-picker';
let data = [];
for(var i=0;i<100;i++){
    data.push(i);
}
 
Picker.init({
    pickerData: data,
    selectedValue: [59],
    onPickerConfirm: data => {
        console.log(data);
    },
    onPickerCancel: data => {
        console.log(data);
    },
    onPickerSelect: data => {
        console.log(data);
    }
});
Picker.show();

Or

react-native-wheel-picker

https://www.npmjs.com/package/react-native-wheel-picker

After Edit: Not Removing Above libraries, if someone needs it ever.

For infinite scroll, you can use and tweak this library: https://www.npmjs.com/package/react-native-infinite-looping-scroll

Here's the link of demo:

https://drive.google.com/uc?id=1re6VhBZ8NZIsPYvN5DMhgveA7ei87N9U

Working Demo, might be bit laggy because running on snack. But works: infinite Scroll Library Demo

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David Scholz
Solution 2