How to get currently visible index in RN flat list
Asked Answered
P

9

66

I have a horizontal flat list where each item is width:300 All I am trying to do is to get index of currently visible item.

<FlatList 
            onScroll={(e) => this.handleScroll(e)} 
            horizontal={true}
            data={this.state.data}
            renderItem...

Tried this:

handleScroll(event) {
    let index = Math.ceil(
      event.nativeEvent.contentOffset.x / 300
    );

And something like this:

handleScroll(event) {
  let contentOffset = event.nativeEvent.contentOffset;
  let index = Math.floor(contentOffset.x / 300);

but nothing is accurate I always get one index up or one index down.
What am I doing wrong and how to get correct current index in flat list?

For example I get to list item that is 8th in a list but I get index 9 or 10.
enter image description here

Pulverize answered 24/8, 2017 at 18:8 Comment(0)
C
96

UPD. thanks to @ch271828n for pointing that onViewableItemsChanged shouldn`t be updated

You can use FlatList onViewableItemsChanged prop to get what you want.

class Test extends React.Component {
  onViewableItemsChanged = ({ viewableItems, changed }) => {
    console.log("Visible items are", viewableItems);
    console.log("Changed in this iteration", changed);
  }

  render () => {
    return (
      <FlatList
        onViewableItemsChanged={this.onViewableItemsChanged }
        viewabilityConfig={{
          itemVisiblePercentThreshold: 50
        }}
      />
    )
  }
}

viewabilityConfig can help you to make whatever you want with viewability settings. In code example 50 means that item is considered visible if it is visible for more than 50 percents.

Config stucture can be found here

Console answered 24/8, 2017 at 19:32 Comment(12)
But how I can know which data to use. Because item is 300p wide and if it's in the middle I will see 20p of left and right items, ok here I can get middle one. But if there are two items how to pick the next to get it's data.Pulverize
This callback returns all three items as visible. Only fully gone items will be marked as not visible.Console
I updated the question with image. When I got two items visible like on image. I need to get only one item data (possibly the one that is more visible than another). Is it possible with this callback somehow?Pulverize
I updated my answer. I think you can experiment with values now and get what you really wantConsole
@RomanOsypov Thanks, works for me. But I want to know, is there any function I can call to get the viewableItems? As I think onViewableItemsChanged will be called multi times upon scrolling events. Hence want to be more efficient. Thanks in advance.Pforzheim
I think you can use throttle or debounce for the callback on this event.Console
In react native 0.54 says: ExceptionsManager.js:65 Invariant Violation: Changing onViewableItemsChanged on the fly is not supportedMessere
@S.HosseinAsadollahi I believe you should move arrow function from FlatList props and provide in immutble manner.Console
<FlatList onViewableItemsChanged={this.onViewableItemsChanged} viewabilityConfig={{ itemVisiblePercentThreshold: 50 }} />Console
I tried this for hours on an Expo 37.0.3 project running in an Android simulator. It won't fire at all. onScroll fires fine, so it's something specific with onViewableItemsChanged, perhaps with Android or this version of Expo.Lillylillywhite
@Roman Osypov I am using functional component and used state to save index the code working with the console.log() but when itry to add the set state code it throw error as 'Error: Changing viewabilityConfig on the fly is not supported'Thursday
@S.HosseinAsadollahi Change the onViewableItemsChanged callback to React.useRef.Wightman
M
31

With related to @fzyzcjy's and @Roman's answers. In react, 16.8+ you can use useCallback to handle the changing onViewableItemsChanged on the fly is not supported error.

    function MyComponent(props) {
        const _onViewableItemsChanged = useCallback(({ viewableItems, changed }) => {
            console.log("Visible items are", viewableItems);
            console.log("Changed in this iteration", changed);
        }, []);
    
        const _viewabilityConfig = {
            itemVisiblePercentThreshold: 50
        }
    
        return <FlatList
                onViewableItemsChanged={_onViewableItemsChanged}
                viewabilityConfig={_viewabilityConfig}
                {...props}
            />;
    }
Merrymaking answered 13/8, 2019 at 1:8 Comment(3)
_viewabilityConfig should be created with useMemo or put it outside function MyComponent() in module scopeTinct
Can I ask why ?Westberg
Eventually used this to do some stuff based on a viewable item on screen, thanksMonkish
M
18

Many thanks to the most-voted answer :) However, it does not work when I try it, raising an error saying that changing onViewableItemsChanged on the fly is not supported. After some search, I find the solution here. Here is the full code that can run without error. The key point is that the two configs should be put as class properties instead of inside the render() function.

class MyComponent extends Component {  
  _onViewableItemsChanged = ({ viewableItems, changed }) => {
    console.log("Visible items are", viewableItems);
    console.log("Changed in this iteration", changed);
  };

  _viewabilityConfig = {
    itemVisiblePercentThreshold: 50
  };

  render() {
    return (
        <FlatList
          onViewableItemsChanged={this._onViewableItemsChanged}
          viewabilityConfig={this._viewabilityConfig}
          {...this.props}
        />
      </View>
    );
  }
}
Mickens answered 4/8, 2018 at 7:16 Comment(4)
I'm running into this problem as well, but I'm using a functional component, not a class. Do you have any ideas to help me?Cuprum
hmm maybe useCallback? I have not written React for years...Mickens
@Cuprum Did you got the answer ?Cipher
A bit late to the party, but here is the link to the issue on Github where a contributor proposed a solution for functional component. Hopefully it will help somebody github.com/facebook/react-native/issues/…Bremsstrahlung
K
10
this.handleScroll = (event) => {
  let yOffset = event.nativeEvent.contentOffset.y
  let contentHeight = event.nativeEvent.contentSize.height
  let value = yOffset / contentHeight
}

<FlatList onScroll={this.handleScroll} />

Get the round-off value to calculate the page-number/index.

Karyolysis answered 12/1, 2018 at 7:48 Comment(2)
Where to put this?Messere
Good suggestion. On Android, onViewableItemsChanged sometimes reports cached items as visible items. So use handleScroll or onMomentumScrollEnd with this suggested calculation is much better.Quarterphase
T
6

In your case, I guess, you might ignore the padding or margin of items. The contentOffsetX or contentOffsetY should be firstViewableItemIndex * (itemWidth + item padding or margin).

enter image description here

As other answers, onViewableItemsChanged would be a better choice to meet your requirement. I wrote an article about how to use it and how it is implemented. https://suelan.github.io/2020/01/21/onViewableItemsChanged/

Tabbitha answered 14/2, 2020 at 16:8 Comment(0)
N
6

A bit late, but for me was different with functional components...

If you are using functional component do it like:

  const onScroll = useCallback((event: NativeSyntheticEvent<NativeScrollEvent>) => {
    const slideSize = event.nativeEvent.layoutMeasurement.width;
    const index = event.nativeEvent.contentOffset.x / slideSize;
    const roundIndex = Math.round(index);
    console.log("roundIndex:", roundIndex);
  }, []);

Then on your FlatList

<FlatList
      data={[2, 3]}
      horizontal={true}
      pagingEnabled={true}
      style={{ flex: 1 }}
      showsHorizontalScrollIndicator={false}
      renderItem={renderItem}
      onScroll={onScroll}
    />
Nalchik answered 21/10, 2021 at 6:38 Comment(1)
The roundIndex is always 0 in this case @manuelApiculate
R
2

While onViewableItemsChanged works for some conditions, it gives the

Error: Changing onViewableItemsChanged on the fly is not supported

error when ever you try to set the state.

This issue is fixed by using viewabilityConfigCallbackPairs instead of onViewableItemsChanged.

import React, {useRef} from 'react';

const App = () => {
    // The name of the function must be onViewableItemsChanged.
    const onViewableItemsChanged = ({viewableItems}) => {
        console.log(viewableItems);
        // Your code here.
    };
    const viewabilityConfigCallbackPairs = useRef([{onViewableItemsChanged}]);

    return (
        <View style={styles.root}>
            <FlatList
              data={data}
              renderItem={renderItem}
              keyExtractor={item => item.id}
              viewabilityConfigCallbackPairs={
                  viewabilityConfigCallbackPairs.current
              }
            />
        </View>
    );
}

Checkout this link for more details.

Rath answered 15/2, 2023 at 12:41 Comment(0)
D
1

this will solve

Invariant Violation: Changing viewabilityConfig on the fly is not supported

, and type script error on viewabilityConfigCallbackPairs.

const onViewableItemsChanged = ({ viewableItems }) => {

console.log(viewableItems)
};
     
const viewabilityConfig = { itemVisiblePercentThreshold: 100 };

const viewabilityConfigCallbackPairs = useRef([
{ viewabilityConfig, onViewableItemsChanged },
]);
                       
<FlatList
       
...
viewabilityConfigCallbackPairs={
viewabilityConfigCallbackPairs.current
}
           
//viewabilityConfig={{ itemVisiblePercentThreshold: 100,  }}<--------remove this 
       />

typeScript:

import {ViewToken} from "react-native";

const onViewableItemsChanged = ({viewableItems}: {viewableItems: ViewToken[]}) => {
...
Dianthe answered 25/2, 2023 at 6:7 Comment(0)
F
-3

Kindly use class-based components. onViewableItemsChanged() method only works with classes otherwise it will give an error

Fretwork answered 22/12, 2022 at 14:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.