Hazim Ali
Hazim Ali

Reputation: 1095

Infinite loop FlatList

I got a component that I want to make some sort like filter page at side something looks like this


A

B

C

D

E

F

G

H

.

.

.


once the user scroll until Z, the user can see A back after Z.. it's endless loop using flatList. How do i archieve that ?


Y

Z

A

B

C

D

.

.

.


right now all i test using onEndReached I move back to initial position but it not a good experience. anyone can give a bit tips or guide?

Thanks!

_renderItem = ({item, index}) => (
    <Text>{item}</Text>
);

<FlatList
  data={['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']}
  keyExtractor={this._keyExtractor}
  renderItem={this._renderItem}
  showsVerticalScrollIndicator={false}
/>

Upvotes: 3

Views: 8778

Answers (2)

Amir Gorji
Amir Gorji

Reputation: 3335

you can use the package: react-native-infinite-looping-scroll from https://github.com/prateek3255/react-native-infinite-looping-scroll. here's it's source code.

import React, { Component } from 'react';
import { FlatList } from 'react-native';
import PropTypes from 'prop-types'

export default class InfiniteScroll extends Component {
    constructor(props) {
        super(props)
        this.state = {
            data: this.props.data,
            end: true,
        }
        length = this.state.data.length
        data = this.state.data.slice()
    }
    checkScroll({ layoutMeasurement, contentOffset, contentSize }) {
        if (this.state.data.length >= length * 3)
            this.setState(prevState => ({
                data: prevState.data.slice(length * 2)
            }))

        if (contentOffset.y <= this.props.offset) {
            this.setState(prevState => ({
                data: [...prevState.data, ...data],
            }), () => this.infListRef.scrollToIndex({ index: length, animated: false }))
        }
        if (layoutMeasurement.height + contentOffset.y >= contentSize.height - this.props.offset && this.state.end) {
            this.setState(prevState => ({
                data: [...prevState.data, ...data],
                end: false
            }))
        }
        else {
            this.setState({
                end: true
            })
        }

    }
    componentDidMount() {
        this.setState(prevState => ({
            data: [...prevState.data, ...prevState.data]
        }))
        setTimeout(() => { this.infListRef.scrollToIndex({ animated: false, index: length }) }, 500);

    }
    render() {
        return (

            <FlatList
                {...this.props}
                ref={(ref) => { this.infListRef = ref; }}
                data={this.state.data}
                renderItem={this.props.renderItem}
                onScroll={({ nativeEvent }) => this.checkScroll(nativeEvent)}
                showsVerticalScrollIndicator={this.props.showsVerticalScrollIndicator}
            />
        );
    }
}

InfiniteScroll.propTypes = {
    offset: PropTypes.number,
    showsVerticalScrollIndicator: PropTypes.bool
}

InfiniteScroll.defaultProps = {
    offset: 20,
    showsVerticalScrollIndicator: false
};

Upvotes: 2

gentlee
gentlee

Reputation: 3717

The best solution without having to scroll to the center I found is using array like object with very big length and use it as data.

const loopedData = {length: 100000} as undefined[]; // created outside component

const initialIndex = originalData
    ? Math.round(loopedData.length / 2 - originalData.length / 2)
    : 0;

<FlatList
  ...
  data={originalData ? loopedData : null}
  extraData={originalData}
  initialScrollIndex={initialIndex}
  getItemLayout={getItemLayout}
  ...
/>

But in render take data from original array:


const render = (info: ListRenderItemInfo<undefined>) => {
  if (!originalData) return null;

  const originalItemIndex = getOriginalItemIndex(
    info.index,
    originalData.length,
    initialIndex,
  );

  return renderItemImpl?.({
    index: originalItemIndex,
    item: originalData[originalItemIndex],
    separators: info.separators,
  });
}

Here are some helpers for implementing this approach:

export const getOriginalItemIndex = (
  index: number,
  originalLength: number,
  initialScrollIndex: number,
) => {
  return (
    (index + originalLength - (initialScrollIndex % originalLength)) %
    originalLength
  );
};

// used for scrolling to nearest index
export const getNearestToCurrentIndex = (
  currentIndex: number,
  originalIndex: number,
  originalIndexTo: number,
  originalLength: number,
) => {
  if (originalIndex === originalIndexTo) return currentIndex;

  const rightDistance =
    originalIndexTo > originalIndex
      ? originalIndexTo - originalIndex
      : originalLength - originalIndex + originalIndexTo;
  const leftDistance = originalLength - rightDistance;
  const result =
    rightDistance <= leftDistance
      ? currentIndex + rightDistance
      : currentIndex - leftDistance;
  return result;
};

You can also recenter list when screen disappears but not unmounted, but not necessary.

Upvotes: 0

Related Questions