FlatList ScrollView Error on any State Change - Invariant Violation: Changing onViewableItemsChanged on the fly is not supported

Based on @woodpav comment. Using functional components and Hooks. Assign both viewabilityConfig and onViewableItemsChanged to refs and use those. Something like below:

  const onViewRef = React.useRef((viewableItems)=> {
      console.log(viewableItems)
      // Use viewable items in state or as intended
  })
  const viewConfigRef = React.useRef({ viewAreaCoveragePercentThreshold: 50 })


<FlatList
      horizontal={true}
      onViewableItemsChanged={onViewRef.current}
      data={Object.keys(cards)}
      keyExtractor={(_, index) => index.toString()}
      viewabilityConfig={viewConfigRef.current}
      renderItem={({ item, index }) => { ... }}
/>

The error "Changing onViewableItemsChanged on the fly is not supported" occurs because when you update the state, you are creating a new onViewableItemsChanged function reference, so you are changing it on the fly.

While the other answer may solve the issue with useRef, it is not the correct hook in this case. You should be using useCallback to return a memoized callback and useState to get the current state without needing to create a new reference to the function.

Here is an example that save all viewed items index on state:

const MyComp = () => {
  const [cardData] = useState(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']);
  const [viewedItems, setViewedItems] = useState([]);

  const handleVieweableItemsChanged = useCallback(({ changed }) => {
    setViewedItems(oldViewedItems => {
      // We can have access to the current state without adding it
      //  to the useCallback dependencies

      let newViewedItems = null;

      changed.forEach(({ index, isViewable }) => {
        if (index != null && isViewable && !oldViewedItems.includes(index)) {
          
           if (newViewedItems == null) {
             newViewedItems = [...oldViewedItems];
           }
           newViewedItems.push(index);
        }
      });

      // If the items didn't change, we return the old items so
      //  an unnecessary re-render is avoided.
      return newViewedItems == null ? oldViewedItems : newViewedItems;
    });

    // Since it has no dependencies, this function is created only once
  }, []);

  function renderItem({ index, item }) {
    const viewed = '' + viewedItems.includes(index);
    return (
      <View>
        <Text>Data: {item}, Viewed: {viewed}</Text>
      </View>
    );
  }

  return (
    <FlatList
      data={cardData}
      onViewableItemsChanged={handleVieweableItemsChanged}
      viewabilityConfig={this.viewabilityConfig}
      renderItem={renderItem}
    />
  );
}

You can see it working on Snack.


You must pass in a function to onViewableItemsChanged that is bound in the constructor of the component and you must set viewabilityConfig as a constant outside of the Flatlist.

Example:

class YourComponent extends Component {

    constructor() {
        super()
        this.onViewableItemsChanged.bind(this)
    }

    onViewableItemsChanged({viewableItems, changed}) {
        console.log('viewableItems', viewableItems)
        console.log('changed', changed)
    }

    viewabilityConfig = {viewAreaCoveragePercentThreshold: 50}

    render() {
        return(
          <FlatList
            data={this.state.cardData}
            horizontal={true}
            pagingEnabled={true}
            showsHorizontalScrollIndicator={false}
            onViewableItemsChanged={this.onViewableItemsChanged}
            viewabilityConfig={this.viewabilityConfig}
            renderItem={({item}) =>
                <View style={{width: width, borderColor: 'white', borderWidth: 20,}}>
                    <Text>Dogs and Cats</Text>
                 </View>}
          />
        )
    }
}