63

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

1110
  • 7,829
  • 55
  • 176
  • 334

9 Answers9

94

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

ch271828n
  • 15,854
  • 5
  • 53
  • 88
Roman Osypov
  • 1,321
  • 12
  • 10
  • 1
    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. – 1110 Aug 24 '17 at 19:35
  • This callback returns all three items as visible. Only fully gone items will be marked as not visible. – Roman Osypov Aug 24 '17 at 19:39
  • 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? – 1110 Aug 24 '17 at 19:44
  • I updated my answer. I think you can experiment with values now and get what you really want – Roman Osypov Aug 24 '17 at 19:55
  • @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. – LiuWenbin_NO. Mar 29 '18 at 09:18
  • I think you can use throttle or debounce for the callback on this event. – Roman Osypov Mar 29 '18 at 09:23
  • 12
    In react native 0.54 says: **ExceptionsManager.js:65 Invariant Violation: Changing onViewableItemsChanged on the fly is not supported** – S.Hossein Asadollahi Apr 09 '18 at 16:23
  • @S.HosseinAsadollahi I believe you should move arrow function from FlatList props and provide in immutble manner. – Roman Osypov Apr 10 '18 at 18:54
  • 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. – pmont May 15 '20 at 06:25
  • @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' – kViN May 29 '20 at 14:52
  • @S.HosseinAsadollahi Change the `onViewableItemsChanged` callback to `React.useRef`. – Masamoto Miyata Jan 04 '23 at 07:10
29

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}
            />;
    }
tiran
  • 2,389
  • 1
  • 16
  • 28
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>
    );
  }
}
ch271828n
  • 15,854
  • 5
  • 53
  • 88
  • 3
    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? – Josiah Mar 09 '20 at 21:41
  • hmm maybe `useCallback`? I have not written React for years... – ch271828n Mar 10 '20 at 00:26
  • @Josiah Did you got the answer ? – Roshan S Feb 17 '21 at 10:11
  • 5
    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 https://github.com/facebook/react-native/issues/30171#issuecomment-711154425 – Mykola Yakovliev Jun 09 '21 at 16:16
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.

dǝɥɔS ʇoıןןƎ
  • 1,674
  • 5
  • 19
  • 42
Sujit
  • 610
  • 7
  • 26
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/

RY_ Zheng
  • 3,041
  • 29
  • 36
5

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}
    />
manuelBetancurt
  • 15,428
  • 33
  • 118
  • 216
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.

Manil Malla
  • 133
  • 8
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[]}) => {
...
Ahmed5G
  • 310
  • 2
  • 8
-2

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

Selaka Nanayakkara
  • 3,296
  • 1
  • 22
  • 42