'Stale state in React causing duplicate API requests

I have a FlatList in React Native which is basically a scrolling list that can trigger a function call when it reaches the end of the list, like a lazy loading component. In order for the list to get the next data I am using pagination, and am storing the current page state in useState. The problem is that when this list updates very fast it ends up using stale pagination state. I'm not sure what to do about that. How do I co-ordinate with FlastList or how do I use an appropriate combo of state and functions to avoid stale state?

const [pagination, setPagination] = React.useState<PaginationParams>({
  limit: 5,
  skip: 0,
});

const getData = async () => {
  // If getData gets called quickly by FlatList, it will use old pagination!
  const response: any = await getData(pagination);
  if (response) {
    setData((prevState) => [...prevState, ...response.data]);
    if (
      typeof pagination.limit !== "undefined" &&
      typeof pagination.skip !== "undefined"
    ) {
      if (response.data.count > pagination.skip) {
        const skip = Math.min(
          pagination.skip + pagination.limit,
          result.data.count
        );
        setPagination((prevState) => ({ ...prevState, skip }));
      }
    }
  }
};

<FlatList
  data={data}
  initialNumToRender={5}
  onEndReached={getData}
  onEndReachedThreshold={0.5}
  renderItem={renderItem}
/>


Solution 1:[1]

The problem appears with React versions before 18 as setData will cause render and setPagination an another one. Pagination info and data will not be in sync after setData -call. One way to solve this would be to put the values into a same state variable:

const [listState, setListState] = React.useState<PaginationParams>({
  data: [],
  pagination: {
    limit: 5,
    skip: 0,
  }
});

const getData = async () => {
  // If getData gets called quickly by FlatList, it will use old pagination!
  const response: any = await getData(pagination);
  if (response) {
    const newState = {
      data: [...listState.data, ...response.data]);
      pagination: // new pagination state to here
    };
    // ...
  }
};

<FlatList
  data={data}
  initialNumToRender={5}
  onEndReached={getData}
  onEndReachedThreshold={0.5}
  renderItem={renderItem}
/>

Another problem with the code is that it will not identify if the fetch is already running. Another fetch call should be blocked until the new data has been retrieved, but this is outside of the scope of your question.

Another recommendation is to use useCallback with event handler to avoid extra rendering. See the explanation in here.

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 Ville Venäläinen